diff --git a/404.html b/404.html index 2b75596..45adbeb 100644 --- a/404.html +++ b/404.html @@ -319,6 +319,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -508,6 +529,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -593,7 +635,7 @@

    404 - Not found

    - + diff --git a/index.html b/index.html index df2c019..e4e0918 100644 --- a/index.html +++ b/index.html @@ -382,6 +382,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -571,6 +592,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -664,14 +706,12 @@

    Home

    Source Code: https://github.com/quantmind/aio-fluid

    Installation

    This is a simple python package you can install via pip:

    -
    pip install aio-fluid
    -
    - +
    pip install aio-fluid
    +

    To install all the dependencies:

    -
    pip install aio-fluid[cli, db, http, log]
    -
    - -

    this includes the following extra dependencies:

    +

    pip install aio-fluid[cli, db, http, log]
    +
    +this includes the following extra dependencies:

    @@ -918,6 +957,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -1316,6 +1376,24 @@ +
  • + +
  • + + +  register_async_handler + + + +
  • + +
  • + + +  unregister_async_handler + + +
  • @@ -1343,9 +1421,8 @@

    Task Consumer

    The task consumer is a TaskManager which is also a Workers that consumes tasks from the task queue and executes them. It can be imported from fluid.scheduler:

    -
    from fastapi.scheduler import TaskConsumer
    -
    - +
    from fastapi.scheduler import TaskConsumer
    +
    @@ -1357,8 +1434,8 @@

    -
    TaskConsumer(**config)
    -
    +
    TaskConsumer(**config)
    +

    @@ -1375,40 +1452,44 @@

    Source code in fluid/scheduler/consumer.py -
    163
    -164
    -165
    -166
    -167
    -168
    -169
    -170
    -171
    -172
    -173
    -174
    -175
    -176
    -177
    -178
    -179
    def __init__(self, **config: Any) -> None:
    -    super().__init__(**config)
    -    Workers.__init__(self)
    -    self._concurrent_tasks: dict[str, dict[str, TaskRun]] = defaultdict(dict)
    -    self._task_to_queue: deque[str | Task] = deque()
    -    self._priority_task_run_queue: deque[TaskRun] = deque()
    -    self._queue_tasks_worker = WorkerFunction(
    -        self._queue_task, name="queue-task-worker"
    -    )
    -    self.add_workers(self._queue_tasks_worker)
    -    for i in range(self.config.max_concurrent_tasks):
    -        worker_name = f"task-worker-{i+1}"
    -        self.add_workers(
    -            WorkerFunction(
    -                partial(self._consume_tasks, worker_name), name=worker_name
    -            )
    -        )
    -
    +
    def __init__(self, **config: Any) -> None:
    +    super().__init__(**config)
    +    Workers.__init__(self)
    +    self._async_dispatcher_worker = AsyncConsumer(AsyncTaskDispatcher())
    +    self._concurrent_tasks: dict[str, dict[str, TaskRun]] = defaultdict(dict)
    +    self._task_to_queue: deque[str | Task] = deque()
    +    self._priority_task_run_queue: deque[TaskRun] = deque()
    +    self._queue_tasks_worker = WorkerFunction(
    +        self._queue_task, name="queue-task-worker"
    +    )
    +    self.add_workers(self._queue_tasks_worker)
    +    self.add_workers(self._async_dispatcher_worker)
    +    for i in range(self.config.max_concurrent_tasks):
    +        worker_name = f"task-worker-{i+1}"
    +        self.add_workers(
    +            WorkerFunction(
    +                partial(self._consume_tasks, worker_name), name=worker_name
    +            )
    +        )
    +
    @@ -1434,10 +1515,12 @@

    -
    worker_name
    -
    +
    worker_name
    +
    + +

    The name of the worker

    @@ -1455,8 +1538,8 @@

    -
    num_workers
    -
    +
    num_workers
    +
    @@ -1476,8 +1559,8 @@

    -
    running
    -
    +
    running
    +
    @@ -1497,8 +1580,8 @@

    -
    state = {}
    -
    +
    state = {}
    +
    @@ -1518,8 +1601,8 @@

    -
    config = TaskManagerConfig(**kwargs)
    -
    +
    config = TaskManagerConfig(**kwargs)
    +
    @@ -1539,8 +1622,8 @@

    -
    dispatcher = TaskDispatcher()
    -
    +
    dispatcher = TaskDispatcher()
    +
    @@ -1560,8 +1643,8 @@

    -
    broker = from_url(broker_url)
    -
    +
    broker = from_url(broker_url)
    +
    @@ -1581,8 +1664,8 @@

    -
    registry
    -
    +
    registry
    +
    @@ -1602,8 +1685,8 @@

    -
    type
    -
    +
    type
    +
    @@ -1623,8 +1706,8 @@

    -
    num_concurrent_tasks
    -
    +
    num_concurrent_tasks
    +
    @@ -1647,17 +1730,17 @@

    -
    status()
    -
    +
    status()
    +
    Source code in fluid/utils/worker.py -
    288
    -289
    async def status(self) -> dict:
    -    return await self._workers.status()
    -
    +
    async def status(self) -> dict:
    +    return await self._workers.status()
    +
    @@ -1671,17 +1754,17 @@

    -
    gracefully_stop()
    -
    +
    gracefully_stop()
    +
    Source code in fluid/utils/worker.py -
    285
    -286
    def gracefully_stop(self) -> None:
    -    self._workers.gracefully_stop()
    -
    +
    def gracefully_stop(self) -> None:
    +    self._workers.gracefully_stop()
    +
    @@ -1695,17 +1778,17 @@

    -
    is_running()
    -
    +
    is_running()
    +
    Source code in fluid/utils/worker.py -
    79
    -80
    def is_running(self) -> bool:
    -    return self._running
    -
    +
    def is_running(self) -> bool:
    +    return self._running
    +
    @@ -1719,17 +1802,17 @@

    -
    is_stopping()
    -
    +
    is_stopping()
    +
    Source code in fluid/utils/worker.py -
    275
    -276
    def is_stopping(self) -> bool:
    -    return self._workers.is_stopping()
    -
    +
    def is_stopping(self) -> bool:
    +    return self._workers.is_stopping()
    +
    @@ -1747,8 +1830,8 @@

    -
    run()
    -
    +
    run()
    +
    @@ -1756,28 +1839,28 @@

    Source code in fluid/utils/worker.py -
    414
    -415
    -416
    -417
    -418
    -419
    -420
    -421
    -422
    -423
    -424
    async def run(self) -> None:
    -    """run the workers"""
    -    with self.start_running():
    -        async with self.safe_run():
    -            workers, _ = self._workers.workers_tasks()
    -            self._workers.workers = tuple(workers)
    -            self._workers.tasks = tuple(
    -                self.create_task(worker) for worker in workers
    -            )
    -            await asyncio.gather(*self._workers.tasks)
    -        await self.shutdown()
    -
    +
    async def run(self) -> None:
    +    """run the workers"""
    +    with self.start_running():
    +        async with self.safe_run():
    +            workers, _ = self._workers.workers_tasks()
    +            self._workers.workers = tuple(workers)
    +            self._workers.tasks = tuple(
    +                self.create_task(worker) for worker in workers
    +            )
    +            await asyncio.gather(*self._workers.tasks)
    +        await self.shutdown()
    +

    @@ -1791,35 +1874,35 @@

    -
    start_running()
    -
    +
    start_running()
    +
    Source code in fluid/utils/worker.py -
    82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    @contextmanager
    -def start_running(self) -> Generator:
    -    if self._running:
    -        raise RuntimeError("Worker is already running")
    -    self._running = True
    -    try:
    -        logger.info("%s started running", self.worker_name)
    -        yield
    -    finally:
    -        self._running = False
    -        logger.warning("%s stopped running", self.worker_name)
    -
    +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    @@ -1833,8 +1916,8 @@

    -
    add_workers(*workers)
    -
    +
    add_workers(*workers)
    +
    @@ -1842,18 +1925,18 @@

    Source code in fluid/utils/worker.py -
    407
    -408
    -409
    -410
    -411
    -412
    def add_workers(self, *workers: Worker) -> None:
    -    """add workers to the workers"""
    -    workers_, _ = self._workers.workers_tasks()
    -    for worker in workers:
    -        if worker not in workers_:
    -            workers_.append(worker)
    -
    +
    def add_workers(self, *workers: Worker) -> None:
    +    """add workers to the workers"""
    +    workers_, _ = self._workers.workers_tasks()
    +    for worker in workers:
    +        if worker not in workers_:
    +            workers_.append(worker)
    +

    @@ -1871,19 +1954,19 @@

    -
    wait_for_exit()
    -
    +
    wait_for_exit()
    +
    Source code in fluid/utils/worker.py -
    426
    -427
    -428
    async def wait_for_exit(self) -> None:
    -    if self._workers_task is not None:
    -        await self._workers_task
    -
    +
    async def wait_for_exit(self) -> None:
    +    if self._workers_task is not None:
    +        await self._workers_task
    +
    @@ -1897,21 +1980,21 @@

    -
    create_task(worker)
    -
    +
    create_task(worker)
    +
    Source code in fluid/utils/worker.py -
    291
    -292
    -293
    -294
    def create_task(self, worker: Worker) -> asyncio.Task:
    -    return asyncio.create_task(
    -        self._run_worker(worker), name=f"{self.worker_name}-{worker.worker_name}"
    -    )
    -
    +
    def create_task(self, worker: Worker) -> asyncio.Task:
    +    return asyncio.create_task(
    +        self._run_worker(worker), name=f"{self.worker_name}-{worker.worker_name}"
    +    )
    +
    @@ -1929,17 +2012,17 @@

    -
    on_shutdown()
    -
    +
    on_shutdown()
    +
    Source code in fluid/scheduler/consumer.py -
    82
    -83
    async def on_shutdown(self) -> None:
    -    await self.broker.close()
    -
    +
    async def on_shutdown(self) -> None:
    +    await self.broker.close()
    +
    @@ -1957,8 +2040,8 @@

    -
    shutdown()
    -
    +
    shutdown()
    +
    @@ -1966,74 +2049,74 @@

    Source code in fluid/utils/worker.py -
    299
    -300
    -301
    -302
    -303
    -304
    -305
    -306
    -307
    -308
    -309
    -310
    -311
    -312
    -313
    -314
    -315
    -316
    -317
    -318
    -319
    -320
    -321
    -322
    -323
    -324
    -325
    -326
    -327
    -328
    -329
    -330
    -331
    -332
    async def shutdown(self) -> None:
    -    """shutdown the workers"""
    -    if self._has_shutdown:
    -        return
    -    self._has_shutdown = True
    -    logger.warning(
    -        "gracefully stopping %d workers: %s",
    -        self.num_workers,
    -        ", ".join(w.worker_name for w in self._workers.workers),
    -    )
    -    self.gracefully_stop()
    -    try:
    -        async with async_timeout.timeout(self._stopping_grace_period):
    -            await self.wait_for_exit()
    -        await self.on_shutdown()
    -        return
    -    except asyncio.TimeoutError:
    -        logger.warning(
    -            "could not stop workers %s gracefully after %s"
    -            " seconds - force shutdown",
    -            ", ".join(
    -                task.get_name() for task in self._workers.tasks if not task.done()
    -            ),
    -            self._stopping_grace_period,
    -        )
    -    except asyncio.CancelledError:
    -        pass
    -    self._force_shutdown = True
    -    self._workers.cancel()
    -    try:
    -        await self.wait_for_exit()
    -    except asyncio.CancelledError:
    -        pass
    -    await self.on_shutdown()
    -
    +
    async def shutdown(self) -> None:
    +    """shutdown the workers"""
    +    if self._has_shutdown:
    +        return
    +    self._has_shutdown = True
    +    logger.warning(
    +        "gracefully stopping %d workers: %s",
    +        self.num_workers,
    +        ", ".join(w.worker_name for w in self._workers.workers),
    +    )
    +    self.gracefully_stop()
    +    try:
    +        async with async_timeout.timeout(self._stopping_grace_period):
    +            await self.wait_for_exit()
    +        await self.on_shutdown()
    +        return
    +    except asyncio.TimeoutError:
    +        logger.warning(
    +            "could not stop workers %s gracefully after %s"
    +            " seconds - force shutdown",
    +            ", ".join(
    +                task.get_name() for task in self._workers.tasks if not task.done()
    +            ),
    +            self._stopping_grace_period,
    +        )
    +    except asyncio.CancelledError:
    +        pass
    +    self._force_shutdown = True
    +    self._workers.cancel()
    +    try:
    +        await self.wait_for_exit()
    +    except asyncio.CancelledError:
    +        pass
    +    await self.on_shutdown()
    +

    @@ -2047,17 +2130,17 @@

    -
    bail_out(reason, code=1)
    -
    +
    bail_out(reason, code=1)
    +
    Source code in fluid/utils/worker.py -
    334
    -335
    def bail_out(self, reason: str, code: int = 1) -> None:
    -    self.gracefully_stop()
    -
    +
    def bail_out(self, reason: str, code: int = 1) -> None:
    +    self.gracefully_stop()
    +
    @@ -2075,8 +2158,8 @@

    -
    safe_run()
    -
    +
    safe_run()
    +
    @@ -2084,42 +2167,42 @@

    Source code in fluid/utils/worker.py -
    337
    -338
    -339
    -340
    -341
    -342
    -343
    -344
    -345
    -346
    -347
    -348
    -349
    -350
    -351
    -352
    -353
    -354
    @asynccontextmanager
    -async def safe_run(self) -> AsyncGenerator:
    -    """Context manager to run a worker safely"""
    -    try:
    -        yield
    -    except asyncio.CancelledError:
    -        if self._force_shutdown:
    -            # we are shutting down, this is expected
    -            pass
    -        raise
    -    except Exception as e:
    -        reason = f"unhandled exception while running workers: {e}"
    -        logger.exception(reason)
    -        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)
    -    else:
    -        # worker finished without error
    -        # make sure we are shutting down
    -        asyncio.get_event_loop().call_soon(self.bail_out, "worker exit", 1)
    -
    +
    @asynccontextmanager
    +async def safe_run(self) -> AsyncGenerator:
    +    """Context manager to run a worker safely"""
    +    try:
    +        yield
    +    except asyncio.CancelledError:
    +        if self._force_shutdown:
    +            # we are shutting down, this is expected
    +            pass
    +        raise
    +    except Exception as e:
    +        reason = f"unhandled exception while running workers: {e}"
    +        logger.exception(reason)
    +        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)
    +    else:
    +        # worker finished without error
    +        # make sure we are shutting down
    +        asyncio.get_event_loop().call_soon(self.bail_out, "worker exit", 1)
    +

    @@ -2133,8 +2216,8 @@

    -
    remove_workers(*workers)
    -
    +
    remove_workers(*workers)
    +
    @@ -2142,22 +2225,22 @@

    Source code in fluid/utils/worker.py -
    430
    -431
    -432
    -433
    -434
    -435
    -436
    -437
    def remove_workers(self, *workers: Worker) -> None:
    -    "remove workers from the workers"
    -    workers_, _ = self._workers.workers_tasks()
    -    for worker in workers:
    -        try:
    -            workers_.remove(worker)
    -        except ValueError:
    -            pass
    -
    +
    def remove_workers(self, *workers: Worker) -> None:
    +    "remove workers from the workers"
    +    workers_, _ = self._workers.workers_tasks()
    +    for worker in workers:
    +        try:
    +            workers_.remove(worker)
    +        except ValueError:
    +            pass
    +

    @@ -2175,8 +2258,8 @@

    -
    startup()
    -
    +
    startup()
    +
    @@ -2184,20 +2267,20 @@

    Source code in fluid/utils/worker.py -
    439
    -440
    -441
    -442
    -443
    -444
    -445
    async def startup(self) -> None:
    -    """start the workers"""
    -    if self._workers_task is None:
    -        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)
    -        for args in self._delayed_callbacks:
    -            self._delayed_callback(*args)
    -        self._delayed_callbacks = []
    -
    +
    async def startup(self) -> None:
    +    """start the workers"""
    +    if self._workers_task is None:
    +        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)
    +        for args in self._delayed_callbacks:
    +            self._delayed_callback(*args)
    +        self._delayed_callbacks = []
    +

    @@ -2211,10 +2294,10 @@

    -
    register_callback(
    -    callback, seconds, jitter=0.0, periodic=False
    -)
    -
    +
    register_callback(
    +    callback, seconds, jitter=0.0, periodic=False
    +)
    +
    @@ -2223,48 +2306,48 @@

    Source code in fluid/utils/worker.py -
    447
    -448
    -449
    -450
    -451
    -452
    -453
    -454
    -455
    -456
    -457
    -458
    -459
    -460
    -461
    -462
    -463
    -464
    -465
    -466
    -467
    def register_callback(
    -    self,
    -    callback: Callable[[], None],
    -    seconds: float,
    -    jitter: float = 0.0,
    -    periodic: bool | float = False,
    -) -> None:
    -    """Register a callback
    -
    -    The callback can be periodic or not.
    -    """
    -    if periodic is True:
    -        periodic_float = seconds
    -    elif periodic is False:
    -        periodic_float = 0.0
    -    else:
    -        periodic_float = periodic
    -    if not self.running:
    -        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))
    -    else:
    -        self._delayed_callback(callback, seconds, jitter, periodic_float)
    -
    +
    def register_callback(
    +    self,
    +    callback: Callable[[], None],
    +    seconds: float,
    +    jitter: float = 0.0,
    +    periodic: bool | float = False,
    +) -> None:
    +    """Register a callback
    +
    +    The callback can be periodic or not.
    +    """
    +    if periodic is True:
    +        periodic_float = seconds
    +    elif periodic is False:
    +        periodic_float = 0.0
    +    else:
    +        periodic_float = periodic
    +    if not self.running:
    +        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))
    +    else:
    +        self._delayed_callback(callback, seconds, jitter, periodic_float)
    +

    @@ -2282,17 +2365,17 @@

    -
    enter_async_context(cm)
    -
    +
    enter_async_context(cm)
    +
    Source code in fluid/scheduler/consumer.py -
    65
    -66
    async def enter_async_context(self, cm: Any) -> Any:
    -    return await self._stack.enter_async_context(cm)
    -
    +
    async def enter_async_context(self, cm: Any) -> Any:
    +    return await self._stack.enter_async_context(cm)
    +
    @@ -2310,8 +2393,8 @@

    -
    execute(task, **params)
    -
    +
    execute(task, **params)
    +
    @@ -2319,16 +2402,16 @@

    Source code in fluid/scheduler/consumer.py -
    76
    -77
    -78
    -79
    -80
    async def execute(self, task: Task | str, **params: Any) -> TaskRun:
    -    """Execute a task and wait for it to finish"""
    -    task_run = self.create_task_run(task, **params)
    -    await task_run.execute()
    -    return task_run
    -
    +
    92
    +93
    +94
    +95
    +96
    async def execute(self, task: Task | str, **params: Any) -> TaskRun:
    +    """Execute a task and wait for it to finish"""
    +    task_run = self.create_task_run(task, **params)
    +    await task_run.execute()
    +    return task_run
    +

    @@ -2342,21 +2425,17 @@

    -
    execute_sync(task, **params)
    -
    +
    execute_sync(task, **params)
    +
    Source code in fluid/scheduler/consumer.py -
    85
    -86
    -87
    -88
    def execute_sync(self, task: Task | str, **params: Any) -> TaskRun:
    -    return asyncio.get_event_loop().run_until_complete(
    -        self._execute_and_exit(task, **params)
    -    )
    -
    +
    def execute_sync(self, task: Task | str, **params: Any) -> TaskRun:
    +    return asyncio.run(self._execute_and_exit(task, **params))
    +
    @@ -2370,8 +2449,8 @@

    -
    register_task(task)
    -
    +
    register_task(task)
    +
    @@ -2380,18 +2459,18 @@

    Source code in fluid/scheduler/consumer.py -
    90
    -91
    -92
    -93
    -94
    -95
    def register_task(self, task: Task) -> None:
    -    """Register a task with the task manager
    -
    -    Only tasks registered can be executed by a task manager
    -    """
    -    self.broker.register_task(task)
    -
    +
    def register_task(self, task: Task) -> None:
    +    """Register a task with the task manager
    +
    +    Only tasks registered can be executed by a task manager
    +    """
    +    self.broker.register_task(task)
    +

    @@ -2409,8 +2488,8 @@

    -
    queue(task, priority=None, **params)
    -
    +
    queue(task, priority=None, **params)
    +
    @@ -2423,42 +2502,42 @@

    Source code in fluid/scheduler/consumer.py -
     97
    - 98
    - 99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    async def queue(
    -    self,
    -    task: str | Task,
    -    priority: TaskPriority | None = None,
    -    **params: Any,
    -) -> TaskRun:
    -    """Queue a task for execution
    -
    -    This methods fires two events:
    -
    -    - queue: when the task is about to be queued
    -    - queued: after the task is queued
    -    """
    -    task_run = self.create_task_run(task, priority=priority, **params)
    -    self.dispatcher.dispatch(task_run)
    -    task_run.set_state(TaskState.queued)
    -    await self.broker.queue_task(task_run)
    -    return task_run
    -
    +
    async def queue(
    +    self,
    +    task: str | Task,
    +    priority: TaskPriority | None = None,
    +    **params: Any,
    +) -> TaskRun:
    +    """Queue a task for execution
    +
    +    This methods fires two events:
    +
    +    - queue: when the task is about to be queued
    +    - queued: after the task is queued
    +    """
    +    task_run = self.create_task_run(task, priority=priority, **params)
    +    self.dispatcher.dispatch(task_run)
    +    task_run.set_state(TaskState.queued)
    +    await self.broker.queue_task(task_run)
    +    return task_run
    +

    @@ -2472,8 +2551,8 @@

    -
    create_task_run(task, run_id='', priority=None, **params)
    -
    +
    create_task_run(task, run_id='', priority=None, **params)
    +
    @@ -2481,42 +2560,42 @@

    Source code in fluid/scheduler/consumer.py -
    116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    def create_task_run(
    -    self,
    -    task: str | Task,
    -    run_id: str = "",
    -    priority: TaskPriority | None = None,
    -    **params: Any,
    -) -> TaskRun:
    -    """Create a TaskRun in `init` state"""
    -    if isinstance(task, str):
    -        task = self.broker.task_from_registry(task)
    -    run_id = run_id or self.broker.new_uuid()
    -    return TaskRun(
    -        id=run_id,
    -        task=task,
    -        priority=priority or task.priority,
    -        params=params,
    -        task_manager=self,
    -    )
    -
    +
    def create_task_run(
    +    self,
    +    task: str | Task,
    +    run_id: str = "",
    +    priority: TaskPriority | None = None,
    +    **params: Any,
    +) -> TaskRun:
    +    """Create a TaskRun in `init` state"""
    +    if isinstance(task, str):
    +        task = self.broker.task_from_registry(task)
    +    run_id = run_id or self.broker.new_uuid()
    +    return TaskRun(
    +        id=run_id,
    +        task=task,
    +        priority=priority or task.priority,
    +        params=params,
    +        task_manager=self,
    +    )
    +

    @@ -2530,25 +2609,25 @@

    -
    register_from_module(module)
    -
    +
    register_from_module(module)
    +
    Source code in fluid/scheduler/consumer.py -
    135
    -136
    -137
    -138
    -139
    -140
    def register_from_module(self, module: Any) -> None:
    -    for name in dir(module):
    -        if name.startswith("_"):
    -            continue
    -        if isinstance(obj := getattr(module, name), Task):
    -            self.register_task(obj)
    -
    +
    def register_from_module(self, module: Any) -> None:
    +    for name in dir(module):
    +        if name.startswith("_"):
    +            continue
    +        if isinstance(obj := getattr(module, name), Task):
    +            self.register_task(obj)
    +
    @@ -2562,8 +2641,8 @@

    -
    cli(**kwargs)
    -
    +
    cli(**kwargs)
    +
    @@ -2571,26 +2650,26 @@

    Source code in fluid/scheduler/consumer.py -
    142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    def cli(self, **kwargs: Any) -> Any:
    -    """Create the task manager command line interface"""
    -    try:
    -        from fluid.scheduler.cli import TaskManagerCLI
    -    except ImportError:
    -        raise ImportError(
    -            "TaskManagerCLI is not available - "
    -            "install with `pip install aio-fluid[cli]`"
    -        ) from None
    -    return TaskManagerCLI(self, **kwargs)
    -
    +
    def cli(self, **kwargs: Any) -> Any:
    +    """Create the task manager command line interface"""
    +    try:
    +        from fluid.scheduler.cli import TaskManagerCLI
    +    except ImportError:
    +        raise ImportError(
    +            "TaskManagerCLI is not available - "
    +            "install with `pip install aio-fluid[cli]`"
    +        ) from None
    +    return TaskManagerCLI(self, **kwargs)
    +

    @@ -2604,17 +2683,17 @@

    -
    sync_queue(task)
    -
    +
    sync_queue(task)
    +
    Source code in fluid/scheduler/consumer.py -
    186
    -187
    def sync_queue(self, task: str | Task) -> None:
    -    self._task_to_queue.appendleft(task)
    -
    +
    def sync_queue(self, task: str | Task) -> None:
    +    self._task_to_queue.appendleft(task)
    +
    @@ -2628,17 +2707,17 @@

    -
    sync_priority_queue(task)
    -
    +
    sync_priority_queue(task)
    +
    Source code in fluid/scheduler/consumer.py -
    189
    -190
    def sync_priority_queue(self, task: str | Task) -> None:
    -    self._priority_task_run_queue.appendleft(self.create_task_run(task))
    -
    +
    def sync_priority_queue(self, task: str | Task) -> None:
    +    self._priority_task_run_queue.appendleft(self.create_task_run(task))
    +
    @@ -2652,8 +2731,8 @@

    -
    num_concurrent_tasks_for(task_name)
    -
    +
    num_concurrent_tasks_for(task_name)
    +
    @@ -2661,12 +2740,12 @@

    Source code in fluid/scheduler/consumer.py -
    192
    -193
    -194
    def num_concurrent_tasks_for(self, task_name: str) -> int:
    -    """The number of concurrent tasks for a given task_name"""
    -    return len(self._concurrent_tasks[task_name])
    -
    +
    def num_concurrent_tasks_for(self, task_name: str) -> int:
    +    """The number of concurrent tasks for a given task_name"""
    +    return len(self._concurrent_tasks[task_name])
    +

    @@ -2684,8 +2763,8 @@

    -
    queue_and_wait(task, *, timeout=2, **params)
    -
    +
    queue_and_wait(task, *, timeout=2, **params)
    +
    @@ -2693,18 +2772,76 @@

    Source code in fluid/scheduler/consumer.py -
    196
    -197
    -198
    -199
    -200
    -201
    async def queue_and_wait(
    -    self, task: str, *, timeout: int = 2, **params: Any
    -) -> TaskRun:
    -    """Queue a task and wait for it to finish"""
    -    with TaskRunWaiter(self) as waiter:
    -        return await waiter.wait(await self.queue(task, **params), timeout=timeout)
    -
    +
    async def queue_and_wait(
    +    self, task: str, *, timeout: int = 2, **params: Any
    +) -> TaskRun:
    +    """Queue a task and wait for it to finish"""
    +    with TaskRunWaiter(self) as waiter:
    +        return await waiter.wait(await self.queue(task, **params), timeout=timeout)
    +
    +
    +

    + +
    + +
    + + +

    + register_async_handler + + +

    +
    register_async_handler(event, handler)
    +
    + +
    + +
    + Source code in fluid/scheduler/consumer.py +
    def register_async_handler(self, event: Event | str, handler: AsyncHandler) -> None:
    +    event = Event.from_string_or_event(event)
    +    self.dispatcher.register_handler(
    +        f"{event.type}.async_dispatch",
    +        self._async_dispatcher_worker.send,
    +    )
    +    self._async_dispatcher_worker.dispatcher.register_handler(event, handler)
    +
    +
    +
    + +
    + +
    + + +

    + unregister_async_handler + + +

    +
    unregister_async_handler(event)
    +
    + +
    + +
    + Source code in fluid/scheduler/consumer.py +
    def unregister_async_handler(self, event: Event | str) -> AsyncHandler | None:
    +    return self._async_dispatcher_worker.dispatcher.unregister_handler(event)
    +
    @@ -2763,7 +2900,7 @@

    - + diff --git a/reference/workers/index.html b/reference/workers/index.html index 57fe5a1..ede52de 100644 --- a/reference/workers/index.html +++ b/reference/workers/index.html @@ -332,6 +332,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -538,6 +559,93 @@ +
  • + +
  • + + +  RunningWorker + + + + +
  • @@ -715,17 +823,17 @@
  • - + -  Workers +  QueueConsumer -
  • + +
  • + + +  QueueConsumerWorker + + + + +
  • + + +  get_message + +
  • - - + +
  • + + +  queue_size + + - - -
  • + + +
  • + + +  run + + - - - - - - -
  • + + + + - - - + +
  • + + +  AsyncConsumer + + + -
  • - - - - - -
    -
    -
    - - - +
    +
    +
    + + + +
    +
    + -
    -
    - - - -
    -
    - - + + + + +
  • + + +  StoppingWorker + + + + + +
  • + +
  • + + +  WorkerFunction + + + + + +
  • + +
  • + + +  QueueConsumer + + + + + +
  • + +
  • + + +  QueueConsumerWorker + + + + + +
  • + +
  • + + +  AsyncConsumer + + + + + +
  • + +
  • + + +  Workers + + + + + +
  • + +
  • + + +  DynamicWorkers + + + + + +
  • + + + + +
    + + + + + +
    +
    + + + + + + + +

    Workers

    +

    Workers are the main building block for asynchronous programming with aio-fluid. They are responsible for running tasks and managing their lifecycle. +There are several worker classes which can be imported from fluid.utils.worker:

    +
    from fastapi.utils.worker import StoppingWorker
    +
    + + +
    + + + +

    + fluid.utils.worker.Worker + + +

    +
    Worker(name='')
    +
    + +
    +

    + Bases: ABC

    + + +

    The base class of a worker that can be run

    + + + + + + +
    + Source code in fluid/utils/worker.py +
    def __init__(self, name: str = "") -> None:
    +    self._name: str = name or underscore(type(self).__name__)
    +
    +
    + + + +
    + + + + + + + +
    + + + +

    + worker_name + + + + property + + +

    +
    worker_name
    +
    + +
    + +

    The name of the worker

    +
    + +
    + +
    + + + +

    + num_workers + + + + property + + +

    +
    num_workers
    +
    + +
    + +

    The number of workers in this worker

    +
    + +
    + + + +
    + + +

    + status + + + + abstractmethod + async + + +

    +
    status()
    +
    + +
    + +

    Get the status of the worker.

    + +
    + Source code in fluid/utils/worker.py +
    52
    +53
    +54
    +55
    +56
    @abstractmethod
    +async def status(self) -> dict:
    +    """
    +    Get the status of the worker.
    +    """
    +
    +
    +
    + +
    + +
    + + +

    + gracefully_stop + + + + abstractmethod + + +

    +
    gracefully_stop()
    +
    + +
    + +

    gracefully stop the worker

    + +
    + Source code in fluid/utils/worker.py +
    58
    +59
    +60
    @abstractmethod
    +def gracefully_stop(self) -> None:
    +    "gracefully stop the worker"
    +
    +
    +
    + +
    + +
    + + +

    + is_running + + + + abstractmethod + + +

    +
    is_running()
    +
    + +
    + +

    Is the worker running?

    + +
    + Source code in fluid/utils/worker.py +
    62
    +63
    +64
    @abstractmethod
    +def is_running(self) -> bool:
    +    """Is the worker running?"""
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + + + abstractmethod + + +

    +
    is_stopping()
    +
    + +
    + +

    Is the worker stopping?

    + +
    + Source code in fluid/utils/worker.py +
    66
    +67
    +68
    @abstractmethod
    +def is_stopping(self) -> bool:
    +    """Is the worker stopping?"""
    +
    +
    +
    + +
    + +
    + + +

    + run + + + + abstractmethod + async + + +

    +
    run()
    +
    + +
    + +

    run the worker

    + +
    + Source code in fluid/utils/worker.py +
    70
    +71
    +72
    @abstractmethod
    +async def run(self) -> None:
    +    """run the worker"""
    +
    +
    +
    + +
    + + + +
    + +
    + +
    + +
    + + + +

    + fluid.utils.worker.RunningWorker + + +

    +
    RunningWorker(name='')
    +
    + +
    +

    + Bases: Worker

    + + +

    A Worker that can be started

    + + + + + + +
    + Source code in fluid/utils/worker.py +
    78
    +79
    +80
    def __init__(self, name: str = "") -> None:
    +    super().__init__(name)
    +    self._running: bool = False
    +
    +
    + + + +
    + + + + + + + +
    + + + +

    + worker_name + + + + property + + +

    +
    worker_name
    +
    + +
    + +

    The name of the worker

    +
    + +
    + +
    + + + +

    + num_workers + + + + property + + +

    +
    num_workers
    +
    + +
    + +

    The number of workers in this worker

    +
    + +
    + + + +
    + + +

    + status + + + + abstractmethod + async + + +

    +
    status()
    +
    + +
    + +

    Get the status of the worker.

    + +
    + Source code in fluid/utils/worker.py +
    52
    +53
    +54
    +55
    +56
    @abstractmethod
    +async def status(self) -> dict:
    +    """
    +    Get the status of the worker.
    +    """
    +
    +
    +
    + +
    + +
    + + +

    + gracefully_stop + + + + abstractmethod + + +

    +
    gracefully_stop()
    +
    + +
    + +

    gracefully stop the worker

    + +
    + Source code in fluid/utils/worker.py +
    58
    +59
    +60
    @abstractmethod
    +def gracefully_stop(self) -> None:
    +    "gracefully stop the worker"
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + + + abstractmethod + + +

    +
    is_stopping()
    +
    + +
    + +

    Is the worker stopping?

    + +
    + Source code in fluid/utils/worker.py +
    66
    +67
    +68
    @abstractmethod
    +def is_stopping(self) -> bool:
    +    """Is the worker stopping?"""
    +
    +
    +
    + +
    + +
    + + +

    + run + + + + abstractmethod + async + + +

    +
    run()
    +
    + +
    + +

    run the worker

    + +
    + Source code in fluid/utils/worker.py +
    70
    +71
    +72
    @abstractmethod
    +async def run(self) -> None:
    +    """run the worker"""
    +
    +
    +
    + +
    + +
    + + +

    + is_running + + +

    +
    is_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_running(self) -> bool:
    +    return self._running
    +
    +
    +
    + +
    + +
    + + +

    + start_running + + +

    +
    start_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    +
    +
    + +
    + + + +
    + +
    + +
    + +
    + + + +

    + fluid.utils.worker.StoppingWorker + + +

    +
    StoppingWorker(name='')
    +
    + +
    +

    + Bases: RunningWorker

    + + +

    A Worker that can be stopped

    + + + + + + +
    + Source code in fluid/utils/worker.py +
    def __init__(self, name: str = "") -> None:
    +    super().__init__(name)
    +    self._stopping: bool = False
    +
    +
    + + + +
    + + + + + + + +
    + + + +

    + worker_name + + + + property + + +

    +
    worker_name
    +
    + +
    + +

    The name of the worker

    +
    + +
    + +
    + + + +

    + num_workers + + + + property + + +

    +
    num_workers
    +
    + +
    + +

    The number of workers in this worker

    +
    + +
    + + + +
    + + +

    + is_running + + +

    +
    is_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_running(self) -> bool:
    +    return self._running
    +
    +
    +
    + +
    + +
    + + +

    + run + + + + abstractmethod + async + + +

    +
    run()
    +
    + +
    + +

    run the worker

    + +
    + Source code in fluid/utils/worker.py +
    70
    +71
    +72
    @abstractmethod
    +async def run(self) -> None:
    +    """run the worker"""
    +
    +
    +
    + +
    + +
    + + +

    + start_running + + +

    +
    start_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + +

    +
    is_stopping()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_stopping(self) -> bool:
    +    return self._stopping
    +
    +
    +
    + +
    + +
    + + +

    + gracefully_stop + + +

    +
    gracefully_stop()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def gracefully_stop(self) -> None:
    +    self._stopping = True
    +
    +
    +
    + +
    + +
    + + +

    + status + + + + async + + +

    +
    status()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def status(self) -> dict:
    +    return {"stopping": self.is_stopping(), "running": self.is_running()}
    +
    +
    +
    + +
    + + + +
    + +
    + +
    + +
    + + + +

    + fluid.utils.worker.WorkerFunction + + +

    +
    WorkerFunction(run_function, heartbeat=0, name='')
    +
    + +
    +

    + Bases: StoppingWorker

    + + +

    A Worker that runs a coroutine function

    + + + + + + +
    + Source code in fluid/utils/worker.py +
    def __init__(
    +    self,
    +    run_function: Callable[[], Awaitable[None]],
    +    heartbeat: float | int = 0,
    +    name: str = "",
    +) -> None:
    +    super().__init__(name=name)
    +    self._run_function = run_function
    +    self._heartbeat = heartbeat
    +
    +
    + + + +
    + + + + + + + +
    + + + +

    + worker_name + + + + property + + +

    +
    worker_name
    +
    + +
    + +

    The name of the worker

    +
    + +
    + +
    + + + +

    + num_workers + + + + property + + +

    +
    num_workers
    +
    + +
    + +

    The number of workers in this worker

    +
    + +
    + + + +
    + + +

    + status + + + + async + + +

    +
    status()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def status(self) -> dict:
    +    return {"stopping": self.is_stopping(), "running": self.is_running()}
    +
    +
    +
    + +
    + +
    + + +

    + gracefully_stop + + +

    +
    gracefully_stop()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def gracefully_stop(self) -> None:
    +    self._stopping = True
    +
    +
    +
    + +
    + +
    + + +

    + is_running + + +

    +
    is_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_running(self) -> bool:
    +    return self._running
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + +

    +
    is_stopping()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_stopping(self) -> bool:
    +    return self._stopping
    +
    +
    +
    + +
    + +
    + + +

    + start_running + + +

    +
    start_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    +
    +
    + +
    + +
    + + +

    + run + + + + async + + +

    +
    run()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def run(self) -> None:
    +    with self.start_running():
    +        while not self.is_stopping():
    +            await self._run_function()
    +            await asyncio.sleep(self._heartbeat)
    +
    +
    +
    + +
    + + + +
    + +
    + +
    + +
    + + + +

    + fluid.utils.worker.QueueConsumer + + +

    +
    QueueConsumer(name='')
    +
    + +
    +

    + Bases: StoppingWorker, MessageProducer[MessageType]

    + + +

    A Worker that can receive messages

    +

    This worker can receive messages but not consume them.

    + + + + + + +
    + Source code in fluid/utils/worker.py +
    def __init__(self, name: str = "") -> None:
    +    super().__init__(name=name)
    +    self._queue: asyncio.Queue[MessageType | None] = asyncio.Queue()
    +
    +
    + + + +
    + + + + + + + +
    + + + +

    + worker_name + + + + property + + +

    +
    worker_name
    +
    + +
    + +

    The name of the worker

    +
    + +
    + +
    + + + +

    + num_workers + + + + property + + +

    +
    num_workers
    +
    + +
    + +

    The number of workers in this worker

    +
    + +
    + + + +
    + + +

    + gracefully_stop + + +

    +
    gracefully_stop()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def gracefully_stop(self) -> None:
    +    self._stopping = True
    +
    +
    +
    + +
    + +
    + + +

    + is_running + + +

    +
    is_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_running(self) -> bool:
    +    return self._running
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + +

    +
    is_stopping()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_stopping(self) -> bool:
    +    return self._stopping
    +
    +
    +
    + +
    + +
    + + +

    + run + + + + abstractmethod + async + + +

    +
    run()
    +
    + +
    + +

    run the worker

    + +
    + Source code in fluid/utils/worker.py +
    70
    +71
    +72
    @abstractmethod
    +async def run(self) -> None:
    +    """run the worker"""
    +
    +
    +
    + +
    + +
    + + +

    + start_running + + +

    +
    start_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    +
    +
    + +
    + +
    + + +

    + get_message + + + + async + + +

    +
    get_message(timeout=0.5)
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def get_message(self, timeout: float = 0.5) -> MessageType | None:
    +    try:
    +        async with asyncio.timeout(timeout):
    +            return await self._queue.get()
    +    except asyncio.TimeoutError:
    +        return None
    +    except (asyncio.CancelledError, RuntimeError):
    +        if not self.is_stopping():
    +            raise
    +    return None
    +
    +
    +
    + +
    + +
    + + +

    + queue_size + + +

    +
    queue_size()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def queue_size(self) -> int:
    +    return self._queue.qsize()
    +
    +
    +
    + +
    + +
    + + +

    + status + + + + async + + +

    +
    status()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def status(self) -> dict:
    +    status = await super().status()
    +    status.update(queue_size=self.queue_size())
    +    return status
    +
    +
    +
    + +
    + +
    + + +

    + send + + +

    +
    send(message)
    +
    + +
    + +

    Send a message into the worker

    + +
    + Source code in fluid/utils/worker.py +
    def send(self, message: MessageType | None) -> None:
    +    """Send a message into the worker"""
    +    self._queue.put_nowait(message)
    +
    +
    +
    + +
    + + + +
    + +
    + +
    + +
    + + + +

    + fluid.utils.worker.QueueConsumerWorker + + +

    +
    QueueConsumerWorker(on_message, name='')
    +
    + +
    +

    + Bases: QueueConsumer[MessageType]

    + + +

    A Worker that can receive and consume messages

    + + + + + + +
    + Source code in fluid/utils/worker.py +
    def __init__(
    +    self,
    +    on_message: Callable[[MessageType], Awaitable[None]],
    +    name: str = "",
    +) -> None:
    +    super().__init__(name=name)
    +    self.on_message = on_message
    +
    +
    + + + +
    + + + + + + + +
    + + + +

    + worker_name + + + + property + + +

    +
    worker_name
    +
    + +
    + +

    The name of the worker

    +
    + +
    + +
    + + + +

    + num_workers + + + + property + + +

    +
    num_workers
    +
    + +
    + +

    The number of workers in this worker

    +
    + +
    + +
    + + + +

    + on_message + + + + instance-attribute + + +

    +
    on_message = on_message
    +
    + +
    +
    + +
    + + + +
    + + +

    + send + + +

    +
    send(message)
    +
    + +
    + +

    Send a message into the worker

    + +
    + Source code in fluid/utils/worker.py +
    def send(self, message: MessageType | None) -> None:
    +    """Send a message into the worker"""
    +    self._queue.put_nowait(message)
    +
    +
    +
    + +
    + +
    + + +

    + status + + + + async + + +

    +
    status()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def status(self) -> dict:
    +    status = await super().status()
    +    status.update(queue_size=self.queue_size())
    +    return status
    +
    +
    +
    + +
    + +
    + + +

    + gracefully_stop + + +

    +
    gracefully_stop()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def gracefully_stop(self) -> None:
    +    self._stopping = True
    +
    +
    +
    + +
    + +
    + + +

    + is_running + + +

    +
    is_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_running(self) -> bool:
    +    return self._running
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + +

    +
    is_stopping()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def is_stopping(self) -> bool:
    +    return self._stopping
    +
    +
    +
    + +
    + +
    + + +

    + start_running + + +

    +
    start_running()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    +
    +
    + +
    + +
    + + +

    + get_message + + + + async + + +

    +
    get_message(timeout=0.5)
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def get_message(self, timeout: float = 0.5) -> MessageType | None:
    +    try:
    +        async with asyncio.timeout(timeout):
    +            return await self._queue.get()
    +    except asyncio.TimeoutError:
    +        return None
    +    except (asyncio.CancelledError, RuntimeError):
    +        if not self.is_stopping():
    +            raise
    +    return None
    +
    +
    +
    + +
    + +
    + + +

    + queue_size + + +

    +
    queue_size()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    def queue_size(self) -> int:
    +    return self._queue.qsize()
    +
    +
    +
    - - +
    + +
    -

    Workers

    -

    Workers are the main building block for asynchronous programming with aio-fluid. They are responsible for running tasks and managing their lifecycle. -There are several worker classes which can be imported from fluid.utils.worker:

    -
    from fastapi.utils.worker import StoppingWorker
    -
    +

    + run + + + + async + + +

    +
    run()
    +
    + +
    + +
    + Source code in fluid/utils/worker.py +
    async def run(self) -> None:
    +    with self.start_running():
    +        while not self.is_stopping():
    +            message = await self.get_message()
    +            if message is not None:
    +                await self.on_message(message)
    +
    +
    +
    + +
    +
    + +
    + +
    +
    -

    - fluid.utils.worker.Worker +

    + fluid.utils.worker.AsyncConsumer -

    -
    Worker(name='')
    -
    + +
    AsyncConsumer(dispatcher, name='')
    +

    - Bases: ABC

    + Bases: QueueConsumer[MessageType]

    -

    The base class of a worker that can be run

    +

    A Worker that can dispatch async callbacks

    @@ -1537,10 +4700,16 @@

    Source code in fluid/utils/worker.py -
    39
    -40
    def __init__(self, name: str = "") -> None:
    -    self._name: str = name or underscore(type(self).__name__)
    -
    +
    def __init__(
    +    self, dispatcher: AsyncDispatcher[MessageType], name: str = ""
    +) -> None:
    +    super().__init__(name)
    +    self.dispatcher: AsyncDispatcher[MessageType] = dispatcher
    +
    @@ -1557,7 +4726,7 @@

    -

    +

    worker_name @@ -1565,11 +4734,13 @@

    property -

    -
    worker_name
    -
    + +
    worker_name
    +
    + +

    The name of the worker

    @@ -1578,7 +4749,7 @@

    -

    +

    num_workers @@ -1586,49 +4757,84 @@

    property -

    -
    num_workers
    -
    + +
    num_workers
    +
    + +

    The number of workers in this worker

    +
    -
    +

    + AsyncCallback -

    - status + + + instance-attribute + + +

    +
    AsyncCallback
    +
    + +
    +
    + +
    + +
    + + + +

    + dispatcher - abstractmethod - async + instance-attribute -

    -
    status()
    -
    + +
    dispatcher = dispatcher
    +
    +
    + +
    + + + +
    + + +

    + send -

    Get the status of the worker.

    + +

    +
    send(message)
    +
    + +
    + +

    Send a message into the worker

    Source code in fluid/utils/worker.py -
    50
    -51
    -52
    -53
    -54
    @abstractmethod
    -async def status(self) -> dict:
    -    """
    -    Get the status of the worker.
    -    """
    -
    +
    def send(self, message: MessageType | None) -> None:
    +    """Send a message into the worker"""
    +    self._queue.put_nowait(message)
    +
    @@ -1637,30 +4843,54 @@

    -

    - gracefully_stop +

    + status - abstractmethod + async -

    -
    gracefully_stop()
    -
    +

    +
    status()
    +
    -

    gracefully stop the worker

    +
    + Source code in fluid/utils/worker.py +
    async def status(self) -> dict:
    +    status = await super().status()
    +    status.update(queue_size=self.queue_size())
    +    return status
    +
    +
    +
    + +
    + +
    + + +

    + gracefully_stop + + +

    +
    gracefully_stop()
    +
    + +
    Source code in fluid/utils/worker.py -
    56
    -57
    -58
    @abstractmethod
    -def gracefully_stop(self) -> None:
    -    "gracefully stop the worker"
    -
    +
    def gracefully_stop(self) -> None:
    +    self._stopping = True
    +
    @@ -1669,30 +4899,88 @@

    -

    +

    is_running - - abstractmethod - +

    +
    is_running()
    +
    -

    -
    is_running()
    -
    +
    + +
    + Source code in fluid/utils/worker.py +
    def is_running(self) -> bool:
    +    return self._running
    +
    +
    +
    + +
    + +
    + + +

    + is_stopping + + +

    +
    is_stopping()
    +
    -

    Is the worker running?

    +
    + Source code in fluid/utils/worker.py +
    def is_stopping(self) -> bool:
    +    return self._stopping
    +
    +
    +
    + +
    + +
    + + +

    + start_running + + +

    +
    start_running()
    +
    + +
    Source code in fluid/utils/worker.py -
    60
    -61
    -62
    @abstractmethod
    -def is_running(self) -> bool:
    -    """Is the worker running?"""
    -
    +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    @@ -1701,30 +4989,66 @@

    -

    - is_stopping +

    + get_message - abstractmethod + async -

    -
    is_stopping()
    -
    +

    +
    get_message(timeout=0.5)
    +
    -

    Is the worker stopping?

    +
    + Source code in fluid/utils/worker.py +
    async def get_message(self, timeout: float = 0.5) -> MessageType | None:
    +    try:
    +        async with asyncio.timeout(timeout):
    +            return await self._queue.get()
    +    except asyncio.TimeoutError:
    +        return None
    +    except (asyncio.CancelledError, RuntimeError):
    +        if not self.is_stopping():
    +            raise
    +    return None
    +
    +
    +
    + +
    + +
    + + +

    + queue_size + + +

    +
    queue_size()
    +
    + +
    Source code in fluid/utils/worker.py -
    64
    -65
    -66
    @abstractmethod
    -def is_stopping(self) -> bool:
    -    """Is the worker stopping?"""
    -
    +
    def queue_size(self) -> int:
    +    return self._queue.qsize()
    +
    @@ -1733,31 +5057,34 @@

    -

    +

    run - abstractmethod async -

    -
    run()
    -
    +

    +
    run()
    +
    -

    run the worker

    -
    Source code in fluid/utils/worker.py -
    68
    -69
    -70
    @abstractmethod
    -async def run(self) -> None:
    -    """run the worker"""
    -
    +
    async def run(self) -> None:
    +    with self.start_running():
    +        while not self.is_stopping():
    +            message = await self.get_message()
    +            if message is not None:
    +                await self.dispatcher.dispatch(message)
    +
    @@ -1775,20 +5102,24 @@

    -

    - fluid.utils.worker.StoppingWorker +

    + fluid.utils.worker.Workers -

    -
    StoppingWorker(name='')
    -
    + +
    Workers(
    +    *workers,
    +    name="",
    +    stopping_grace_period=STOPPING_GRACE_PERIOD
    +)
    +

    - Bases: RunningWorker

    + Bases: MultipleWorkers

    -

    A Worker that can be stopped

    +

    A worker managing several workers

    @@ -1797,12 +5128,32 @@

    Source code in fluid/utils/worker.py -
     98
    - 99
    -100
    def __init__(self, name: str = "") -> None:
    -    super().__init__(name)
    -    self._stopping: bool = False
    -
    +
    def __init__(
    +    self,
    +    *workers: Worker,
    +    name: str = "",
    +    stopping_grace_period: int = settings.STOPPING_GRACE_PERIOD,
    +) -> None:
    +    super().__init__(
    +        *workers, name=name, stopping_grace_period=stopping_grace_period
    +    )
    +    self._workers_task: asyncio.Task | None = None
    +    self._delayed_callbacks: list[
    +        tuple[Callable[[], None], float, float, float]
    +    ] = []
    +
    @@ -1819,7 +5170,7 @@

    -

    +

    worker_name @@ -1827,11 +5178,13 @@

    property -

    -
    worker_name
    -
    + +
    worker_name
    +
    + +

    The name of the worker

    @@ -1840,7 +5193,7 @@

    -

    +

    num_workers @@ -1848,69 +5201,61 @@

    property -

    -
    num_workers
    -
    + +
    num_workers
    +
    +
    -
    +

    + running -

    - is_running + + property + -

    -
    is_running()
    -
    + +
    running
    +
    - -
    - Source code in fluid/utils/worker.py -
    79
    -80
    def is_running(self) -> bool:
    -    return self._running
    -
    -
    + +
    -

    - run +

    + status - abstractmethod async -

    -
    run()
    -
    + +
    status()
    +
    -

    run the worker

    -
    Source code in fluid/utils/worker.py -
    68
    -69
    -70
    @abstractmethod
    -async def run(self) -> None:
    -    """run the worker"""
    -
    +
    async def status(self) -> dict:
    +    return await self._workers.status()
    +
    @@ -1919,40 +5264,22 @@

    -

    - start_running +

    + gracefully_stop -

    -
    start_running()
    -
    +

    +
    gracefully_stop()
    +
    Source code in fluid/utils/worker.py -
    82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    @contextmanager
    -def start_running(self) -> Generator:
    -    if self._running:
    -        raise RuntimeError("Worker is already running")
    -    self._running = True
    -    try:
    -        logger.info("%s started running", self.worker_name)
    -        yield
    -    finally:
    -        self._running = False
    -        logger.warning("%s stopped running", self.worker_name)
    -
    +
    def gracefully_stop(self) -> None:
    +    self._workers.gracefully_stop()
    +
    @@ -1961,22 +5288,22 @@

    -

    - is_stopping +

    + is_running -

    -
    is_stopping()
    -
    + +
    is_running()
    +
    Source code in fluid/utils/worker.py -
    102
    -103
    def is_stopping(self) -> bool:
    -    return self._stopping
    -
    +
    def is_running(self) -> bool:
    +    return self._running
    +
    @@ -1985,22 +5312,22 @@

    -

    - gracefully_stop +

    + is_stopping -

    -
    gracefully_stop()
    -
    +

    +
    is_stopping()
    +
    Source code in fluid/utils/worker.py -
    105
    -106
    def gracefully_stop(self) -> None:
    -    self._stopping = True
    -
    +
    def is_stopping(self) -> bool:
    +    return self._workers.is_stopping()
    +
    @@ -2009,160 +5336,278 @@

    -

    - status - +

    + start_running - - async - -

    -
    status()
    -
    + +
    start_running()
    +
    Source code in fluid/utils/worker.py -
    108
    -109
    async def status(self) -> dict:
    -    return {"stopping": self.is_stopping(), "running": self.is_running()}
    -
    +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    +
    -
    - -
    - -
    - -
    - - - -

    - fluid.utils.worker.WorkerFunction - - -

    -
    WorkerFunction(run_function, heartbeat=0, name='')
    -
    - -
    -

    - Bases: StoppingWorker

    - - +

    + create_task +

    +
    create_task(worker)
    +
    +
    +
    + Source code in fluid/utils/worker.py +
    def create_task(self, worker: Worker) -> asyncio.Task:
    +    return asyncio.create_task(
    +        self._run_worker(worker), name=f"{self.worker_name}-{worker.worker_name}"
    +    )
    +
    +
    +
    -
    - Source code in fluid/utils/worker.py -
    113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    def __init__(
    -    self,
    -    run_function: Callable[[], Awaitable[None]],
    -    heartbeat: float | int = 0,
    -    name: str = "",
    -) -> None:
    -    super().__init__(name=name)
    -    self._run_function = run_function
    -    self._heartbeat = heartbeat
    -
    -
    +
    +
    -
    +

    + on_shutdown + + async + +

    +
    on_shutdown()
    +
    +
    +

    called after the workers are stopped

    +
    + Source code in fluid/utils/worker.py +
    async def on_shutdown(self) -> None:
    +    """called after the workers are stopped"""
    +
    +
    +
    -
    +
    +
    -

    - worker_name +

    + shutdown - property + async -

    -
    worker_name
    -
    + +
    shutdown()
    +
    -
    -
    +

    shutdown the workers

    -
    +
    + Source code in fluid/utils/worker.py +
    async def shutdown(self) -> None:
    +    """shutdown the workers"""
    +    if self._has_shutdown:
    +        return
    +    self._has_shutdown = True
    +    logger.warning(
    +        "gracefully stopping %d workers: %s",
    +        self.num_workers,
    +        ", ".join(w.worker_name for w in self._workers.workers),
    +    )
    +    self.gracefully_stop()
    +    try:
    +        async with async_timeout.timeout(self._stopping_grace_period):
    +            await self.wait_for_exit()
    +        await self.on_shutdown()
    +        return
    +    except asyncio.TimeoutError:
    +        logger.warning(
    +            "could not stop workers %s gracefully after %s"
    +            " seconds - force shutdown",
    +            ", ".join(
    +                task.get_name() for task in self._workers.tasks if not task.done()
    +            ),
    +            self._stopping_grace_period,
    +        )
    +    except asyncio.CancelledError:
    +        pass
    +    self._force_shutdown = True
    +    self._workers.cancel()
    +    try:
    +        await self.wait_for_exit()
    +    except asyncio.CancelledError:
    +        pass
    +    await self.on_shutdown()
    +
    +
    +
    +
    +
    -

    - num_workers +

    + bail_out - - property - -

    -
    num_workers
    -
    + +
    bail_out(reason, code=1)
    +
    + +
    + Source code in fluid/utils/worker.py +
    def bail_out(self, reason: str, code: int = 1) -> None:
    +    self.gracefully_stop()
    +
    +
    - -
    -

    - status +

    + safe_run async -

    -
    status()
    -
    + +
    safe_run()
    +
    +

    Context manager to run a worker safely

    +
    Source code in fluid/utils/worker.py -
    108
    -109
    async def status(self) -> dict:
    -    return {"stopping": self.is_stopping(), "running": self.is_running()}
    -
    +
    @asynccontextmanager
    +async def safe_run(self) -> AsyncGenerator:
    +    """Context manager to run a worker safely"""
    +    try:
    +        yield
    +    except asyncio.CancelledError:
    +        if self._force_shutdown:
    +            # we are shutting down, this is expected
    +            pass
    +        raise
    +    except Exception as e:
    +        reason = f"unhandled exception while running workers: {e}"
    +        logger.exception(reason)
    +        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)
    +    else:
    +        # worker finished without error
    +        # make sure we are shutting down
    +        asyncio.get_event_loop().call_soon(self.bail_out, "worker exit", 1)
    +
    @@ -2171,22 +5616,32 @@

    -

    - gracefully_stop +

    + add_workers -

    -
    gracefully_stop()
    -
    +

    +
    add_workers(*workers)
    +
    +

    add workers to the workers

    +
    Source code in fluid/utils/worker.py -
    105
    -106
    def gracefully_stop(self) -> None:
    -    self._stopping = True
    -
    +
    def add_workers(self, *workers: Worker) -> None:
    +    """add workers to the workers"""
    +    workers_, _ = self._workers.workers_tasks()
    +    for worker in workers:
    +        if worker not in workers_:
    +            workers_.append(worker)
    +
    @@ -2195,22 +5650,46 @@

    -

    - is_running +

    + run -

    -
    is_running()
    -
    + + async + + + +
    run()
    +
    +

    run the workers

    +
    Source code in fluid/utils/worker.py -
    79
    -80
    def is_running(self) -> bool:
    -    return self._running
    -
    +
    async def run(self) -> None:
    +    """run the workers"""
    +    with self.start_running():
    +        async with self.safe_run():
    +            workers, _ = self._workers.workers_tasks()
    +            self._workers.workers = tuple(workers)
    +            self._workers.tasks = tuple(
    +                self.create_task(worker) for worker in workers
    +            )
    +            await asyncio.gather(*self._workers.tasks)
    +        await self.shutdown()
    +
    @@ -2219,22 +5698,28 @@

    -

    - is_stopping +

    + wait_for_exit -

    -
    is_stopping()
    -
    + + async + + +

    +
    wait_for_exit()
    +
    Source code in fluid/utils/worker.py -
    102
    -103
    def is_stopping(self) -> bool:
    -    return self._stopping
    -
    +
    async def wait_for_exit(self) -> None:
    +    if self._workers_task is not None:
    +        await self._workers_task
    +
    @@ -2243,40 +5728,36 @@

    -

    - start_running +

    + remove_workers -

    -
    start_running()
    -
    +

    +
    remove_workers(*workers)
    +
    +

    remove workers from the workers

    +
    Source code in fluid/utils/worker.py -
    82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    @contextmanager
    -def start_running(self) -> Generator:
    -    if self._running:
    -        raise RuntimeError("Worker is already running")
    -    self._running = True
    -    try:
    -        logger.info("%s started running", self.worker_name)
    -        yield
    -    finally:
    -        self._running = False
    -        logger.warning("%s stopped running", self.worker_name)
    -
    +
    def remove_workers(self, *workers: Worker) -> None:
    +    "remove workers from the workers"
    +    workers_, _ = self._workers.workers_tasks()
    +    for worker in workers:
    +        try:
    +            workers_.remove(worker)
    +        except ValueError:
    +            pass
    +
    @@ -2285,32 +5766,105 @@

    -

    - run +

    + startup async -

    -
    run()
    -
    + +
    startup()
    +
    + +
    + +

    start the workers

    + +
    + Source code in fluid/utils/worker.py +
    async def startup(self) -> None:
    +    """start the workers"""
    +    if self._workers_task is None:
    +        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)
    +        for args in self._delayed_callbacks:
    +            self._delayed_callback(*args)
    +        self._delayed_callbacks = []
    +
    +
    +
    + +
    + +
    + + +

    + register_callback + + +

    +
    register_callback(
    +    callback, seconds, jitter=0.0, periodic=False
    +)
    +
    +

    Register a callback

    +

    The callback can be periodic or not.

    +
    Source code in fluid/utils/worker.py -
    123
    -124
    -125
    -126
    -127
    async def run(self) -> None:
    -    with self.start_running():
    -        while not self.is_stopping():
    -            await self._run_function()
    -            await asyncio.sleep(self._heartbeat)
    -
    +
    def register_callback(
    +    self,
    +    callback: Callable[[], None],
    +    seconds: float,
    +    jitter: float = 0.0,
    +    periodic: bool | float = False,
    +) -> None:
    +    """Register a callback
    +
    +    The callback can be periodic or not.
    +    """
    +    if periodic is True:
    +        periodic_float = seconds
    +    elif periodic is False:
    +        periodic_float = 0.0
    +    else:
    +        periodic_float = periodic
    +    if not self.running:
    +        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))
    +    else:
    +        self._delayed_callback(callback, seconds, jitter, periodic_float)
    +
    @@ -2328,25 +5882,24 @@

    -

    - fluid.utils.worker.Workers +

    + fluid.utils.worker.DynamicWorkers -

    -
    Workers(
    -    *workers,
    -    name="",
    -    stopping_grace_period=STOPPING_GRACE_PERIOD
    -)
    -
    + +
    DynamicWorkers(
    +    *workers,
    +    name="",
    +    heartbeat=0.1,
    +    stopping_grace_period=STOPPING_GRACE_PERIOD
    +)
    +

    Bases: MultipleWorkers

    -

    A worker managing several workers

    - @@ -2354,32 +5907,34 @@

    Source code in fluid/utils/worker.py -
    388
    -389
    -390
    -391
    -392
    -393
    -394
    -395
    -396
    -397
    -398
    -399
    -400
    def __init__(
    -    self,
    -    *workers: Worker,
    -    name: str = "",
    -    stopping_grace_period: int = settings.STOPPING_GRACE_PERIOD,
    -) -> None:
    -    super().__init__(
    -        *workers, name=name, stopping_grace_period=stopping_grace_period
    -    )
    -    self._workers_task: asyncio.Task | None = None
    -    self._delayed_callbacks: list[
    -        tuple[Callable[[], None], float, float, float]
    -    ] = []
    -
    +
    def __init__(
    +    self,
    +    *workers: Worker,
    +    name: str = "",
    +    heartbeat: float | int = 0.1,
    +    stopping_grace_period: int = settings.STOPPING_GRACE_PERIOD,
    +) -> None:
    +    super().__init__(name)
    +    self._heartbeat = heartbeat
    +    self._workers = WorkerTasks()
    +    self._has_shutdown = False
    +    self._force_shutdown = False
    +    self._stopping_grace_period = stopping_grace_period
    +    self.add_workers(*workers)
    +
    @@ -2396,7 +5951,7 @@

    -

    +

    worker_name @@ -2404,32 +5959,13 @@

    property -

    -
    worker_name
    -
    + +
    worker_name
    +
    -
    - -
    -
    - - - -

    - num_workers - - - - property - - -

    -
    num_workers
    -
    - -
    +

    The name of the worker

    @@ -2438,17 +5974,17 @@

    -

    - running +

    + num_workers property -

    -
    running
    -
    + +
    num_workers
    +
    @@ -2460,7 +5996,7 @@

    -

    +

    status @@ -2468,18 +6004,18 @@

    async -

    -
    status()
    -
    +

    +
    status()
    +
    Source code in fluid/utils/worker.py -
    288
    -289
    async def status(self) -> dict:
    -    return await self._workers.status()
    -
    +
    async def status(self) -> dict:
    +    return await self._workers.status()
    +
    @@ -2488,22 +6024,22 @@

    -

    +

    gracefully_stop -

    -
    gracefully_stop()
    -
    +

    +
    gracefully_stop()
    +
    Source code in fluid/utils/worker.py -
    285
    -286
    def gracefully_stop(self) -> None:
    -    self._workers.gracefully_stop()
    -
    +
    def gracefully_stop(self) -> None:
    +    self._workers.gracefully_stop()
    +
    @@ -2512,22 +6048,22 @@

    -

    +

    is_running -

    -
    is_running()
    -
    +

    +
    is_running()
    +
    Source code in fluid/utils/worker.py -
    79
    -80
    def is_running(self) -> bool:
    -    return self._running
    -
    +
    def is_running(self) -> bool:
    +    return self._running
    +
    @@ -2536,22 +6072,22 @@

    -

    +

    is_stopping -

    -
    is_stopping()
    -
    +

    +
    is_stopping()
    +
    Source code in fluid/utils/worker.py -
    275
    -276
    def is_stopping(self) -> bool:
    -    return self._workers.is_stopping()
    -
    +
    def is_stopping(self) -> bool:
    +    return self._workers.is_stopping()
    +
    @@ -2560,40 +6096,40 @@

    -

    +

    start_running -

    -
    start_running()
    -
    +

    +
    start_running()
    +
    Source code in fluid/utils/worker.py -
    82
    -83
    -84
    -85
    -86
    -87
    -88
    -89
    -90
    -91
    -92
    @contextmanager
    -def start_running(self) -> Generator:
    -    if self._running:
    -        raise RuntimeError("Worker is already running")
    -    self._running = True
    -    try:
    -        logger.info("%s started running", self.worker_name)
    -        yield
    -    finally:
    -        self._running = False
    -        logger.warning("%s stopped running", self.worker_name)
    -
    +
    85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    @contextmanager
    +def start_running(self) -> Generator:
    +    if self._running:
    +        raise RuntimeError("Worker is already running")
    +    self._running = True
    +    try:
    +        logger.info("%s started running", self.worker_name)
    +        yield
    +    finally:
    +        self._running = False
    +        logger.warning("%s stopped running", self.worker_name)
    +
    @@ -2602,26 +6138,26 @@

    -

    +

    create_task -

    -
    create_task(worker)
    -
    +

    +
    create_task(worker)
    +
    Source code in fluid/utils/worker.py -
    291
    -292
    -293
    -294
    def create_task(self, worker: Worker) -> asyncio.Task:
    -    return asyncio.create_task(
    -        self._run_worker(worker), name=f"{self.worker_name}-{worker.worker_name}"
    -    )
    -
    +
    def create_task(self, worker: Worker) -> asyncio.Task:
    +    return asyncio.create_task(
    +        self._run_worker(worker), name=f"{self.worker_name}-{worker.worker_name}"
    +    )
    +
    @@ -2630,7 +6166,7 @@

    -

    +

    on_shutdown @@ -2638,9 +6174,9 @@

    async -

    -
    on_shutdown()
    -
    +

    +
    on_shutdown()
    +
    @@ -2648,10 +6184,10 @@

    Source code in fluid/utils/worker.py -
    296
    -297
    async def on_shutdown(self) -> None:
    -    """called after the workers are stopped"""
    -
    +
    async def on_shutdown(self) -> None:
    +    """called after the workers are stopped"""
    +

    @@ -2660,7 +6196,7 @@

    -

    +

    shutdown @@ -2668,9 +6204,9 @@

    async -

    -
    shutdown()
    -
    +

    +
    shutdown()
    +
    @@ -2678,74 +6214,74 @@

    Source code in fluid/utils/worker.py -
    299
    -300
    -301
    -302
    -303
    -304
    -305
    -306
    -307
    -308
    -309
    -310
    -311
    -312
    -313
    -314
    -315
    -316
    -317
    -318
    -319
    -320
    -321
    -322
    -323
    -324
    -325
    -326
    -327
    -328
    -329
    -330
    -331
    -332
    async def shutdown(self) -> None:
    -    """shutdown the workers"""
    -    if self._has_shutdown:
    -        return
    -    self._has_shutdown = True
    -    logger.warning(
    -        "gracefully stopping %d workers: %s",
    -        self.num_workers,
    -        ", ".join(w.worker_name for w in self._workers.workers),
    -    )
    -    self.gracefully_stop()
    -    try:
    -        async with async_timeout.timeout(self._stopping_grace_period):
    -            await self.wait_for_exit()
    -        await self.on_shutdown()
    -        return
    -    except asyncio.TimeoutError:
    -        logger.warning(
    -            "could not stop workers %s gracefully after %s"
    -            " seconds - force shutdown",
    -            ", ".join(
    -                task.get_name() for task in self._workers.tasks if not task.done()
    -            ),
    -            self._stopping_grace_period,
    -        )
    -    except asyncio.CancelledError:
    -        pass
    -    self._force_shutdown = True
    -    self._workers.cancel()
    -    try:
    -        await self.wait_for_exit()
    -    except asyncio.CancelledError:
    -        pass
    -    await self.on_shutdown()
    -
    +
    async def shutdown(self) -> None:
    +    """shutdown the workers"""
    +    if self._has_shutdown:
    +        return
    +    self._has_shutdown = True
    +    logger.warning(
    +        "gracefully stopping %d workers: %s",
    +        self.num_workers,
    +        ", ".join(w.worker_name for w in self._workers.workers),
    +    )
    +    self.gracefully_stop()
    +    try:
    +        async with async_timeout.timeout(self._stopping_grace_period):
    +            await self.wait_for_exit()
    +        await self.on_shutdown()
    +        return
    +    except asyncio.TimeoutError:
    +        logger.warning(
    +            "could not stop workers %s gracefully after %s"
    +            " seconds - force shutdown",
    +            ", ".join(
    +                task.get_name() for task in self._workers.tasks if not task.done()
    +            ),
    +            self._stopping_grace_period,
    +        )
    +    except asyncio.CancelledError:
    +        pass
    +    self._force_shutdown = True
    +    self._workers.cancel()
    +    try:
    +        await self.wait_for_exit()
    +    except asyncio.CancelledError:
    +        pass
    +    await self.on_shutdown()
    +

    @@ -2754,22 +6290,22 @@

    -

    +

    bail_out -

    -
    bail_out(reason, code=1)
    -
    +

    +
    bail_out(reason, code=1)
    +
    Source code in fluid/utils/worker.py -
    334
    -335
    def bail_out(self, reason: str, code: int = 1) -> None:
    -    self.gracefully_stop()
    -
    +
    def bail_out(self, reason: str, code: int = 1) -> None:
    +    self.gracefully_stop()
    +
    @@ -2778,7 +6314,7 @@

    -

    +

    safe_run @@ -2786,9 +6322,9 @@

    async -

    -
    safe_run()
    -
    +

    +
    safe_run()
    +
    @@ -2796,42 +6332,42 @@

    Source code in fluid/utils/worker.py -
    337
    -338
    -339
    -340
    -341
    -342
    -343
    -344
    -345
    -346
    -347
    -348
    -349
    -350
    -351
    -352
    -353
    -354
    @asynccontextmanager
    -async def safe_run(self) -> AsyncGenerator:
    -    """Context manager to run a worker safely"""
    -    try:
    -        yield
    -    except asyncio.CancelledError:
    -        if self._force_shutdown:
    -            # we are shutting down, this is expected
    -            pass
    -        raise
    -    except Exception as e:
    -        reason = f"unhandled exception while running workers: {e}"
    -        logger.exception(reason)
    -        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)
    -    else:
    -        # worker finished without error
    -        # make sure we are shutting down
    -        asyncio.get_event_loop().call_soon(self.bail_out, "worker exit", 1)
    -
    +
    @asynccontextmanager
    +async def safe_run(self) -> AsyncGenerator:
    +    """Context manager to run a worker safely"""
    +    try:
    +        yield
    +    except asyncio.CancelledError:
    +        if self._force_shutdown:
    +            # we are shutting down, this is expected
    +            pass
    +        raise
    +    except Exception as e:
    +        reason = f"unhandled exception while running workers: {e}"
    +        logger.exception(reason)
    +        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)
    +    else:
    +        # worker finished without error
    +        # make sure we are shutting down
    +        asyncio.get_event_loop().call_soon(self.bail_out, "worker exit", 1)
    +

    @@ -2840,32 +6376,39 @@

    -

    +

    add_workers -

    -
    add_workers(*workers)
    -
    +

    +
    add_workers(*workers)
    +

    add workers to the workers

    +

    They can be added while the workers are running.

    Source code in fluid/utils/worker.py -
    407
    -408
    -409
    -410
    -411
    -412
    def add_workers(self, *workers: Worker) -> None:
    -    """add workers to the workers"""
    -    workers_, _ = self._workers.workers_tasks()
    -    for worker in workers:
    -        if worker not in workers_:
    -            workers_.append(worker)
    -
    +
    def add_workers(self, *workers: Worker) -> None:
    +    """add workers to the workers
    +
    +    They can be added while the workers are running.
    +    """
    +    workers_, tasks_ = self._workers.workers_tasks()
    +    for worker in workers:
    +        workers_.append(worker)
    +        tasks_.append(self.create_task(worker))
    +
    @@ -2874,7 +6417,7 @@

    -

    +

    run @@ -2882,38 +6425,30 @@

    async -

    -
    run()
    -
    +

    +
    run()
    +
    -

    run the workers

    -
    Source code in fluid/utils/worker.py -
    414
    -415
    -416
    -417
    -418
    -419
    -420
    -421
    -422
    -423
    -424
    async def run(self) -> None:
    -    """run the workers"""
    -    with self.start_running():
    -        async with self.safe_run():
    -            workers, _ = self._workers.workers_tasks()
    -            self._workers.workers = tuple(workers)
    -            self._workers.tasks = tuple(
    -                self.create_task(worker) for worker in workers
    -            )
    -            await asyncio.gather(*self._workers.tasks)
    -        await self.shutdown()
    -
    +
    async def run(self) -> None:
    +    with self.start_running():
    +        while not self.is_stopping():
    +            for worker, task in zip(self._workers.workers, self._workers.tasks):
    +                if worker.is_stopping() or task.done():
    +                    break
    +            await asyncio.sleep(self._heartbeat)
    +        await self.shutdown()
    +
    @@ -2922,7 +6457,7 @@

    -

    +

    wait_for_exit @@ -2930,165 +6465,18 @@

    async -

    -
    wait_for_exit()
    -
    - -
    - -
    - Source code in fluid/utils/worker.py -
    426
    -427
    -428
    async def wait_for_exit(self) -> None:
    -    if self._workers_task is not None:
    -        await self._workers_task
    -
    -
    -
    - -
    - -
    - - -

    - remove_workers - - -

    -
    remove_workers(*workers)
    -
    - -
    - -

    remove workers from the workers

    - -
    - Source code in fluid/utils/worker.py -
    430
    -431
    -432
    -433
    -434
    -435
    -436
    -437
    def remove_workers(self, *workers: Worker) -> None:
    -    "remove workers from the workers"
    -    workers_, _ = self._workers.workers_tasks()
    -    for worker in workers:
    -        try:
    -            workers_.remove(worker)
    -        except ValueError:
    -            pass
    -
    -
    -
    - -
    - -
    - - -

    - startup - - - - async - - -

    -
    startup()
    -
    - -
    - -

    start the workers

    - -
    - Source code in fluid/utils/worker.py -
    439
    -440
    -441
    -442
    -443
    -444
    -445
    async def startup(self) -> None:
    -    """start the workers"""
    -    if self._workers_task is None:
    -        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)
    -        for args in self._delayed_callbacks:
    -            self._delayed_callback(*args)
    -        self._delayed_callbacks = []
    -
    -
    -
    - -
    - -
    - - -

    - register_callback - - -

    -
    register_callback(
    -    callback, seconds, jitter=0.0, periodic=False
    -)
    -
    +

    +
    wait_for_exit()
    +
    -

    Register a callback

    -

    The callback can be periodic or not.

    -
    Source code in fluid/utils/worker.py -
    447
    -448
    -449
    -450
    -451
    -452
    -453
    -454
    -455
    -456
    -457
    -458
    -459
    -460
    -461
    -462
    -463
    -464
    -465
    -466
    -467
    def register_callback(
    -    self,
    -    callback: Callable[[], None],
    -    seconds: float,
    -    jitter: float = 0.0,
    -    periodic: bool | float = False,
    -) -> None:
    -    """Register a callback
    -
    -    The callback can be periodic or not.
    -    """
    -    if periodic is True:
    -        periodic_float = seconds
    -    elif periodic is False:
    -        periodic_float = 0.0
    -    else:
    -        periodic_float = periodic
    -    if not self.running:
    -        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))
    -    else:
    -        self._delayed_callback(callback, seconds, jitter, periodic_float)
    -
    +
    async def wait_for_exit(self) -> None:
    +    await asyncio.gather(*self._workers.tasks)
    +
    @@ -3147,7 +6535,7 @@

    - + diff --git a/search/search_index.json b/search/search_index.json index bf68bf8..5f30676 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"

    Aio Fluid

    Async utilities for backend python services developed by Quantmind.

    Documentation: https://quantmind.github.io/aio-fluid

    Source Code: https://github.com/quantmind/aio-fluid

    "},{"location":"#installation","title":"Installation","text":"

    This is a simple python package you can install via pip:

    pip install aio-fluid\n

    To install all the dependencies:

    pip install aio-fluid[cli, db, http, log]\n

    this includes the following extra dependencies:

    • cli for the command line interface using click and rich
    • db for database support with asyncpg and sqlalchemy
    • http for http client support with httpx and aiohttp
    • log for JSON logging support with python-json-logger
    "},{"location":"#development","title":"Development","text":"

    You can run the examples via

    poetry run python -m examples.main\n
    "},{"location":"reference/","title":"Introduction","text":"

    Here's the reference or code API, the classes, functions, parameters, attributes, and all the aio-fluid parts you can use in your applications.

    "},{"location":"reference/task_broker/","title":"Task Broker","text":"

    It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskBroker\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker","title":"fluid.scheduler.TaskBroker","text":"
    TaskBroker(url)\n

    Bases: ABC

    Source code in fluid/scheduler/broker.py
    def __init__(self, url: URL) -> None:\n    self.url: URL = url\n    self.registry: TaskRegistry = TaskRegistry()\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.url","title":"url instance-attribute","text":"
    url = url\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.registry","title":"registry instance-attribute","text":"
    registry = TaskRegistry()\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.task_queue_names","title":"task_queue_names abstractmethod property","text":"
    task_queue_names\n

    Names of the task queues

    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.queue_task","title":"queue_task abstractmethod async","text":"
    queue_task(task_run)\n

    Queue a task

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def queue_task(self, task_run: TaskRun) -> None:\n    \"\"\"Queue a task\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.get_task_run","title":"get_task_run abstractmethod async","text":"
    get_task_run(task_manager)\n

    Get a Task run from the task queue

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def get_task_run(self, task_manager: TaskManager) -> TaskRun | None:\n    \"\"\"Get a Task run from the task queue\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.queue_length","title":"queue_length abstractmethod async","text":"
    queue_length()\n

    Length of task queues

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def queue_length(self) -> dict[str, int]:\n    \"\"\"Length of task queues\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.get_tasks_info","title":"get_tasks_info abstractmethod async","text":"
    get_tasks_info(*task_names)\n

    List of TaskInfo objects

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def get_tasks_info(self, *task_names: str) -> list[TaskInfo]:\n    \"\"\"List of TaskInfo objects\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.update_task","title":"update_task abstractmethod async","text":"
    update_task(task, params)\n

    Update a task dynamic parameters

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def update_task(self, task: Task, params: dict[str, Any]) -> TaskInfo:\n    \"\"\"Update a task dynamic parameters\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.close","title":"close abstractmethod async","text":"
    close()\n

    Close the broker on shutdown

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def close(self) -> None:\n    \"\"\"Close the broker on shutdown\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.lock","title":"lock abstractmethod","text":"
    lock(name, timeout=None)\n

    Create a lock

    Source code in fluid/scheduler/broker.py
    @abstractmethod\ndef lock(self, name: str, timeout: float | None = None) -> Lock:\n    \"\"\"Create a lock\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.new_uuid","title":"new_uuid","text":"
    new_uuid()\n
    Source code in fluid/scheduler/broker.py
    def new_uuid(self) -> str:\n    return uuid4().hex\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.filter_tasks","title":"filter_tasks async","text":"
    filter_tasks(scheduled=None, enabled=None)\n
    Source code in fluid/scheduler/broker.py
    async def filter_tasks(\n    self,\n    scheduled: bool | None = None,\n    enabled: bool | None = None,\n) -> list[Task]:\n    task_info = await self.get_tasks_info()\n    task_map = {info.name: info for info in task_info}\n    tasks = []\n    for task in self.registry.values():\n        if scheduled is not None and bool(task.schedule) is not scheduled:\n            continue\n        if enabled is not None and task_map[task.name].enabled is not enabled:\n            continue\n        tasks.append(task)\n    return tasks\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.task_from_registry","title":"task_from_registry","text":"
    task_from_registry(task)\n
    Source code in fluid/scheduler/broker.py
    def task_from_registry(self, task: str | Task) -> Task:\n    if isinstance(task, Task):\n        self.register_task(task)\n        return task\n    else:\n        if task_ := self.registry.get(task):\n            return task_\n        raise UnknownTaskError(task)\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.register_task","title":"register_task","text":"
    register_task(task)\n
    Source code in fluid/scheduler/broker.py
    def register_task(self, task: Task) -> None:\n    self.registry[task.name] = task\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.enable_task","title":"enable_task async","text":"
    enable_task(task_name, enable=True)\n

    Enable or disable a registered task

    Source code in fluid/scheduler/broker.py
    async def enable_task(self, task_name: str, enable: bool = True) -> TaskInfo:\n    \"\"\"Enable or disable a registered task\"\"\"\n    task = self.registry.get(task_name)\n    if not task:\n        raise UnknownTaskError(task_name)\n    return await self.update_task(task, dict(enabled=enable))\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.from_url","title":"from_url classmethod","text":"
    from_url(url='')\n
    Source code in fluid/scheduler/broker.py
    @classmethod\ndef from_url(cls, url: str = \"\") -> TaskBroker:\n    p = URL(url or broker_url_from_env())\n    if factory := _brokers.get(p.scheme):\n        return factory(p)\n    raise RuntimeError(f\"Invalid broker {p}\")\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.register_broker","title":"register_broker classmethod","text":"
    register_broker(name, factory)\n
    Source code in fluid/scheduler/broker.py
    @classmethod\ndef register_broker(cls, name: str, factory: type[TaskBroker]) -> None:\n    _brokers[name] = factory\n
    "},{"location":"reference/task_manager/","title":"Task Manager","text":"

    It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskManager\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager","title":"fluid.scheduler.TaskManager","text":"
    TaskManager(**kwargs)\n

    The task manager is the main entry point for managing tasks

    Source code in fluid/scheduler/consumer.py
    def __init__(self, **kwargs: Any) -> None:\n    self.state: dict[str, Any] = {}\n    self.config: TaskManagerConfig = TaskManagerConfig(**kwargs)\n    self.dispatcher = TaskDispatcher()\n    self.broker = TaskBroker.from_url(self.config.broker_url)\n    self._stack = AsyncExitStack()\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.state","title":"state instance-attribute","text":"
    state = {}\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.config","title":"config instance-attribute","text":"
    config = TaskManagerConfig(**kwargs)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.dispatcher","title":"dispatcher instance-attribute","text":"
    dispatcher = TaskDispatcher()\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.broker","title":"broker instance-attribute","text":"
    broker = from_url(broker_url)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.registry","title":"registry property","text":"
    registry\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.type","title":"type property","text":"
    type\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.enter_async_context","title":"enter_async_context async","text":"
    enter_async_context(cm)\n
    Source code in fluid/scheduler/consumer.py
    async def enter_async_context(self, cm: Any) -> Any:\n    return await self._stack.enter_async_context(cm)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.execute","title":"execute async","text":"
    execute(task, **params)\n

    Execute a task and wait for it to finish

    Source code in fluid/scheduler/consumer.py
    async def execute(self, task: Task | str, **params: Any) -> TaskRun:\n    \"\"\"Execute a task and wait for it to finish\"\"\"\n    task_run = self.create_task_run(task, **params)\n    await task_run.execute()\n    return task_run\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n
    Source code in fluid/scheduler/consumer.py
    async def on_shutdown(self) -> None:\n    await self.broker.close()\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.execute_sync","title":"execute_sync","text":"
    execute_sync(task, **params)\n
    Source code in fluid/scheduler/consumer.py
    def execute_sync(self, task: Task | str, **params: Any) -> TaskRun:\n    return asyncio.get_event_loop().run_until_complete(\n        self._execute_and_exit(task, **params)\n    )\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.register_task","title":"register_task","text":"
    register_task(task)\n

    Register a task with the task manager

    Only tasks registered can be executed by a task manager

    Source code in fluid/scheduler/consumer.py
    def register_task(self, task: Task) -> None:\n    \"\"\"Register a task with the task manager\n\n    Only tasks registered can be executed by a task manager\n    \"\"\"\n    self.broker.register_task(task)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.queue","title":"queue async","text":"
    queue(task, priority=None, **params)\n

    Queue a task for execution

    This methods fires two events:

    • queue: when the task is about to be queued
    • queued: after the task is queued
    Source code in fluid/scheduler/consumer.py
    async def queue(\n    self,\n    task: str | Task,\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Queue a task for execution\n\n    This methods fires two events:\n\n    - queue: when the task is about to be queued\n    - queued: after the task is queued\n    \"\"\"\n    task_run = self.create_task_run(task, priority=priority, **params)\n    self.dispatcher.dispatch(task_run)\n    task_run.set_state(TaskState.queued)\n    await self.broker.queue_task(task_run)\n    return task_run\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.create_task_run","title":"create_task_run","text":"
    create_task_run(task, run_id='', priority=None, **params)\n

    Create a TaskRun in init state

    Source code in fluid/scheduler/consumer.py
    def create_task_run(\n    self,\n    task: str | Task,\n    run_id: str = \"\",\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Create a TaskRun in `init` state\"\"\"\n    if isinstance(task, str):\n        task = self.broker.task_from_registry(task)\n    run_id = run_id or self.broker.new_uuid()\n    return TaskRun(\n        id=run_id,\n        task=task,\n        priority=priority or task.priority,\n        params=params,\n        task_manager=self,\n    )\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.register_from_module","title":"register_from_module","text":"
    register_from_module(module)\n
    Source code in fluid/scheduler/consumer.py
    def register_from_module(self, module: Any) -> None:\n    for name in dir(module):\n        if name.startswith(\"_\"):\n            continue\n        if isinstance(obj := getattr(module, name), Task):\n            self.register_task(obj)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.cli","title":"cli","text":"
    cli(**kwargs)\n

    Create the task manager command line interface

    Source code in fluid/scheduler/consumer.py
    def cli(self, **kwargs: Any) -> Any:\n    \"\"\"Create the task manager command line interface\"\"\"\n    try:\n        from fluid.scheduler.cli import TaskManagerCLI\n    except ImportError:\n        raise ImportError(\n            \"TaskManagerCLI is not available - \"\n            \"install with `pip install aio-fluid[cli]`\"\n        ) from None\n    return TaskManagerCLI(self, **kwargs)\n
    "},{"location":"reference/task_run/","title":"Task Run","text":"

    It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskRun\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun","title":"fluid.scheduler.TaskRun","text":"

    Bases: BaseModel

    A TaskRun contains all the data generated by a Task run

    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.id","title":"id instance-attribute","text":"
    id\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.task","title":"task instance-attribute","text":"
    task\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.priority","title":"priority instance-attribute","text":"
    priority\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.params","title":"params instance-attribute","text":"
    params\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.state","title":"state class-attribute instance-attribute","text":"
    state = init\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.task_manager","title":"task_manager class-attribute instance-attribute","text":"
    task_manager = Field(exclude=True, repr=False)\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.queued","title":"queued class-attribute instance-attribute","text":"
    queued = None\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.start","title":"start class-attribute instance-attribute","text":"
    start = None\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.end","title":"end class-attribute instance-attribute","text":"
    end = None\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.logger","title":"logger property","text":"
    logger\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.in_queue","title":"in_queue property","text":"
    in_queue\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.duration","title":"duration property","text":"
    duration\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.duration_ms","title":"duration_ms property","text":"
    duration_ms\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.total","title":"total property","text":"
    total\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.name","title":"name property","text":"
    name\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.name_id","title":"name_id property","text":"
    name_id\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.is_done","title":"is_done property","text":"
    is_done\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.is_failure","title":"is_failure property","text":"
    is_failure\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.execute","title":"execute async","text":"
    execute()\n
    Source code in fluid/scheduler/models.py
    async def execute(self) -> None:\n    try:\n        self.set_state(TaskState.running)\n        await self.task.executor(self)\n    except Exception:\n        self.set_state(TaskState.failure)\n        raise\n    else:\n        self.set_state(TaskState.success)\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.serialize_task","title":"serialize_task","text":"
    serialize_task(task, _info)\n
    Source code in fluid/scheduler/models.py
    @field_serializer(\"task\")\ndef serialize_task(self, task: Task, _info: Any) -> str:\n    return task.name\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.params_dump_json","title":"params_dump_json","text":"
    params_dump_json()\n
    Source code in fluid/scheduler/models.py
    def params_dump_json(self) -> str:\n    return self.task.params_dump_json(self.params)\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.set_state","title":"set_state","text":"
    set_state(state, state_time=None)\n
    Source code in fluid/scheduler/models.py
    def set_state(\n    self,\n    state: TaskState,\n    state_time: datetime | None = None,\n) -> None:\n    if self.state == state:\n        return\n    state_time = state_time or utcnow()\n    match (self.state, state):\n        case (TaskState.init, TaskState.queued):\n            self.queued = state_time\n            self.state = state\n            self._dispatch()\n        case (TaskState.init, _):\n            self.set_state(TaskState.queued, state_time)\n            self.set_state(state, state_time)\n        case (TaskState.queued, TaskState.running):\n            self.start = state_time\n            self.state = state\n            self._dispatch()\n        case (\n            TaskState.queued,\n            TaskState.success\n            | TaskState.aborted\n            | TaskState.rate_limited\n            | TaskState.failure,\n        ):\n            self.set_state(TaskState.running, state_time)\n            self.set_state(state, state_time)\n        case (\n            TaskState.running,\n            TaskState.success\n            | TaskState.aborted\n            | TaskState.rate_limited\n            | TaskState.failure,\n        ):\n            self.end = state_time\n            self.state = state\n            self._dispatch()\n        case _:\n            raise TaskRunError(f\"invalid state transition {self.state} -> {state}\")\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.lock","title":"lock","text":"
    lock(timeout)\n
    Source code in fluid/scheduler/models.py
    def lock(self, timeout: float | None) -> Lock:\n    return self.task_manager.broker.lock(self.name, timeout=timeout)\n
    "},{"location":"reference/tast_consumer/","title":"Task Consumer","text":"

    The task consumer is a TaskManager which is also a Workers that consumes tasks from the task queue and executes them. It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskConsumer\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer","title":"fluid.scheduler.TaskConsumer","text":"
    TaskConsumer(**config)\n

    Bases: TaskManager, Workers

    The Task Consumer is a Task Manager responsible for consuming tasks from a task queue

    Source code in fluid/scheduler/consumer.py
    def __init__(self, **config: Any) -> None:\n    super().__init__(**config)\n    Workers.__init__(self)\n    self._concurrent_tasks: dict[str, dict[str, TaskRun]] = defaultdict(dict)\n    self._task_to_queue: deque[str | Task] = deque()\n    self._priority_task_run_queue: deque[TaskRun] = deque()\n    self._queue_tasks_worker = WorkerFunction(\n        self._queue_task, name=\"queue-task-worker\"\n    )\n    self.add_workers(self._queue_tasks_worker)\n    for i in range(self.config.max_concurrent_tasks):\n        worker_name = f\"task-worker-{i+1}\"\n        self.add_workers(\n            WorkerFunction(\n                partial(self._consume_tasks, worker_name), name=worker_name\n            )\n        )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.worker_name","title":"worker_name property","text":"
    worker_name\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.running","title":"running property","text":"
    running\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.state","title":"state instance-attribute","text":"
    state = {}\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.config","title":"config instance-attribute","text":"
    config = TaskManagerConfig(**kwargs)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.dispatcher","title":"dispatcher instance-attribute","text":"
    dispatcher = TaskDispatcher()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.broker","title":"broker instance-attribute","text":"
    broker = from_url(broker_url)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.registry","title":"registry property","text":"
    registry\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.type","title":"type property","text":"
    type\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.num_concurrent_tasks","title":"num_concurrent_tasks property","text":"
    num_concurrent_tasks\n

    The number of concurrent_tasks running in the consumer

    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return await self._workers.status()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._workers.gracefully_stop()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._workers.is_stopping()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.run","title":"run async","text":"
    run()\n

    run the workers

    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    \"\"\"run the workers\"\"\"\n    with self.start_running():\n        async with self.safe_run():\n            workers, _ = self._workers.workers_tasks()\n            self._workers.workers = tuple(workers)\n            self._workers.tasks = tuple(\n                self.create_task(worker) for worker in workers\n            )\n            await asyncio.gather(*self._workers.tasks)\n        await self.shutdown()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.add_workers","title":"add_workers","text":"
    add_workers(*workers)\n

    add workers to the workers

    Source code in fluid/utils/worker.py
    def add_workers(self, *workers: Worker) -> None:\n    \"\"\"add workers to the workers\"\"\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        if worker not in workers_:\n            workers_.append(worker)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.wait_for_exit","title":"wait_for_exit async","text":"
    wait_for_exit()\n
    Source code in fluid/utils/worker.py
    async def wait_for_exit(self) -> None:\n    if self._workers_task is not None:\n        await self._workers_task\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.create_task","title":"create_task","text":"
    create_task(worker)\n
    Source code in fluid/utils/worker.py
    def create_task(self, worker: Worker) -> asyncio.Task:\n    return asyncio.create_task(\n        self._run_worker(worker), name=f\"{self.worker_name}-{worker.worker_name}\"\n    )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n
    Source code in fluid/scheduler/consumer.py
    async def on_shutdown(self) -> None:\n    await self.broker.close()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.shutdown","title":"shutdown async","text":"
    shutdown()\n

    shutdown the workers

    Source code in fluid/utils/worker.py
    async def shutdown(self) -> None:\n    \"\"\"shutdown the workers\"\"\"\n    if self._has_shutdown:\n        return\n    self._has_shutdown = True\n    logger.warning(\n        \"gracefully stopping %d workers: %s\",\n        self.num_workers,\n        \", \".join(w.worker_name for w in self._workers.workers),\n    )\n    self.gracefully_stop()\n    try:\n        async with async_timeout.timeout(self._stopping_grace_period):\n            await self.wait_for_exit()\n        await self.on_shutdown()\n        return\n    except asyncio.TimeoutError:\n        logger.warning(\n            \"could not stop workers %s gracefully after %s\"\n            \" seconds - force shutdown\",\n            \", \".join(\n                task.get_name() for task in self._workers.tasks if not task.done()\n            ),\n            self._stopping_grace_period,\n        )\n    except asyncio.CancelledError:\n        pass\n    self._force_shutdown = True\n    self._workers.cancel()\n    try:\n        await self.wait_for_exit()\n    except asyncio.CancelledError:\n        pass\n    await self.on_shutdown()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.bail_out","title":"bail_out","text":"
    bail_out(reason, code=1)\n
    Source code in fluid/utils/worker.py
    def bail_out(self, reason: str, code: int = 1) -> None:\n    self.gracefully_stop()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.safe_run","title":"safe_run async","text":"
    safe_run()\n

    Context manager to run a worker safely

    Source code in fluid/utils/worker.py
    @asynccontextmanager\nasync def safe_run(self) -> AsyncGenerator:\n    \"\"\"Context manager to run a worker safely\"\"\"\n    try:\n        yield\n    except asyncio.CancelledError:\n        if self._force_shutdown:\n            # we are shutting down, this is expected\n            pass\n        raise\n    except Exception as e:\n        reason = f\"unhandled exception while running workers: {e}\"\n        logger.exception(reason)\n        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)\n    else:\n        # worker finished without error\n        # make sure we are shutting down\n        asyncio.get_event_loop().call_soon(self.bail_out, \"worker exit\", 1)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.remove_workers","title":"remove_workers","text":"
    remove_workers(*workers)\n

    remove workers from the workers

    Source code in fluid/utils/worker.py
    def remove_workers(self, *workers: Worker) -> None:\n    \"remove workers from the workers\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        try:\n            workers_.remove(worker)\n        except ValueError:\n            pass\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.startup","title":"startup async","text":"
    startup()\n

    start the workers

    Source code in fluid/utils/worker.py
    async def startup(self) -> None:\n    \"\"\"start the workers\"\"\"\n    if self._workers_task is None:\n        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)\n        for args in self._delayed_callbacks:\n            self._delayed_callback(*args)\n        self._delayed_callbacks = []\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_callback","title":"register_callback","text":"
    register_callback(\n    callback, seconds, jitter=0.0, periodic=False\n)\n

    Register a callback

    The callback can be periodic or not.

    Source code in fluid/utils/worker.py
    def register_callback(\n    self,\n    callback: Callable[[], None],\n    seconds: float,\n    jitter: float = 0.0,\n    periodic: bool | float = False,\n) -> None:\n    \"\"\"Register a callback\n\n    The callback can be periodic or not.\n    \"\"\"\n    if periodic is True:\n        periodic_float = seconds\n    elif periodic is False:\n        periodic_float = 0.0\n    else:\n        periodic_float = periodic\n    if not self.running:\n        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))\n    else:\n        self._delayed_callback(callback, seconds, jitter, periodic_float)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.enter_async_context","title":"enter_async_context async","text":"
    enter_async_context(cm)\n
    Source code in fluid/scheduler/consumer.py
    async def enter_async_context(self, cm: Any) -> Any:\n    return await self._stack.enter_async_context(cm)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.execute","title":"execute async","text":"
    execute(task, **params)\n

    Execute a task and wait for it to finish

    Source code in fluid/scheduler/consumer.py
    async def execute(self, task: Task | str, **params: Any) -> TaskRun:\n    \"\"\"Execute a task and wait for it to finish\"\"\"\n    task_run = self.create_task_run(task, **params)\n    await task_run.execute()\n    return task_run\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.execute_sync","title":"execute_sync","text":"
    execute_sync(task, **params)\n
    Source code in fluid/scheduler/consumer.py
    def execute_sync(self, task: Task | str, **params: Any) -> TaskRun:\n    return asyncio.get_event_loop().run_until_complete(\n        self._execute_and_exit(task, **params)\n    )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_task","title":"register_task","text":"
    register_task(task)\n

    Register a task with the task manager

    Only tasks registered can be executed by a task manager

    Source code in fluid/scheduler/consumer.py
    def register_task(self, task: Task) -> None:\n    \"\"\"Register a task with the task manager\n\n    Only tasks registered can be executed by a task manager\n    \"\"\"\n    self.broker.register_task(task)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.queue","title":"queue async","text":"
    queue(task, priority=None, **params)\n

    Queue a task for execution

    This methods fires two events:

    • queue: when the task is about to be queued
    • queued: after the task is queued
    Source code in fluid/scheduler/consumer.py
    async def queue(\n    self,\n    task: str | Task,\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Queue a task for execution\n\n    This methods fires two events:\n\n    - queue: when the task is about to be queued\n    - queued: after the task is queued\n    \"\"\"\n    task_run = self.create_task_run(task, priority=priority, **params)\n    self.dispatcher.dispatch(task_run)\n    task_run.set_state(TaskState.queued)\n    await self.broker.queue_task(task_run)\n    return task_run\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.create_task_run","title":"create_task_run","text":"
    create_task_run(task, run_id='', priority=None, **params)\n

    Create a TaskRun in init state

    Source code in fluid/scheduler/consumer.py
    def create_task_run(\n    self,\n    task: str | Task,\n    run_id: str = \"\",\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Create a TaskRun in `init` state\"\"\"\n    if isinstance(task, str):\n        task = self.broker.task_from_registry(task)\n    run_id = run_id or self.broker.new_uuid()\n    return TaskRun(\n        id=run_id,\n        task=task,\n        priority=priority or task.priority,\n        params=params,\n        task_manager=self,\n    )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_from_module","title":"register_from_module","text":"
    register_from_module(module)\n
    Source code in fluid/scheduler/consumer.py
    def register_from_module(self, module: Any) -> None:\n    for name in dir(module):\n        if name.startswith(\"_\"):\n            continue\n        if isinstance(obj := getattr(module, name), Task):\n            self.register_task(obj)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.cli","title":"cli","text":"
    cli(**kwargs)\n

    Create the task manager command line interface

    Source code in fluid/scheduler/consumer.py
    def cli(self, **kwargs: Any) -> Any:\n    \"\"\"Create the task manager command line interface\"\"\"\n    try:\n        from fluid.scheduler.cli import TaskManagerCLI\n    except ImportError:\n        raise ImportError(\n            \"TaskManagerCLI is not available - \"\n            \"install with `pip install aio-fluid[cli]`\"\n        ) from None\n    return TaskManagerCLI(self, **kwargs)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.sync_queue","title":"sync_queue","text":"
    sync_queue(task)\n
    Source code in fluid/scheduler/consumer.py
    def sync_queue(self, task: str | Task) -> None:\n    self._task_to_queue.appendleft(task)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.sync_priority_queue","title":"sync_priority_queue","text":"
    sync_priority_queue(task)\n
    Source code in fluid/scheduler/consumer.py
    def sync_priority_queue(self, task: str | Task) -> None:\n    self._priority_task_run_queue.appendleft(self.create_task_run(task))\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.num_concurrent_tasks_for","title":"num_concurrent_tasks_for","text":"
    num_concurrent_tasks_for(task_name)\n

    The number of concurrent tasks for a given task_name

    Source code in fluid/scheduler/consumer.py
    def num_concurrent_tasks_for(self, task_name: str) -> int:\n    \"\"\"The number of concurrent tasks for a given task_name\"\"\"\n    return len(self._concurrent_tasks[task_name])\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.queue_and_wait","title":"queue_and_wait async","text":"
    queue_and_wait(task, *, timeout=2, **params)\n

    Queue a task and wait for it to finish

    Source code in fluid/scheduler/consumer.py
    async def queue_and_wait(\n    self, task: str, *, timeout: int = 2, **params: Any\n) -> TaskRun:\n    \"\"\"Queue a task and wait for it to finish\"\"\"\n    with TaskRunWaiter(self) as waiter:\n        return await waiter.wait(await self.queue(task, **params), timeout=timeout)\n
    "},{"location":"reference/workers/","title":"Workers","text":"

    Workers are the main building block for asynchronous programming with aio-fluid. They are responsible for running tasks and managing their lifecycle. There are several worker classes which can be imported from fluid.utils.worker:

    from fastapi.utils.worker import StoppingWorker\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker","title":"fluid.utils.worker.Worker","text":"
    Worker(name='')\n

    Bases: ABC

    The base class of a worker that can be run

    Source code in fluid/utils/worker.py
    def __init__(self, name: str = \"\") -> None:\n    self._name: str = name or underscore(type(self).__name__)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.worker_name","title":"worker_name property","text":"
    worker_name\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.status","title":"status abstractmethod async","text":"
    status()\n

    Get the status of the worker.

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def status(self) -> dict:\n    \"\"\"\n    Get the status of the worker.\n    \"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.gracefully_stop","title":"gracefully_stop abstractmethod","text":"
    gracefully_stop()\n

    gracefully stop the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef gracefully_stop(self) -> None:\n    \"gracefully stop the worker\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.is_running","title":"is_running abstractmethod","text":"
    is_running()\n

    Is the worker running?

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef is_running(self) -> bool:\n    \"\"\"Is the worker running?\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.is_stopping","title":"is_stopping abstractmethod","text":"
    is_stopping()\n

    Is the worker stopping?

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef is_stopping(self) -> bool:\n    \"\"\"Is the worker stopping?\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.run","title":"run abstractmethod async","text":"
    run()\n

    run the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def run(self) -> None:\n    \"\"\"run the worker\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker","title":"fluid.utils.worker.StoppingWorker","text":"
    StoppingWorker(name='')\n

    Bases: RunningWorker

    A Worker that can be stopped

    Source code in fluid/utils/worker.py
    def __init__(self, name: str = \"\") -> None:\n    super().__init__(name)\n    self._stopping: bool = False\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.worker_name","title":"worker_name property","text":"
    worker_name\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.run","title":"run abstractmethod async","text":"
    run()\n

    run the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def run(self) -> None:\n    \"\"\"run the worker\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return {\"stopping\": self.is_stopping(), \"running\": self.is_running()}\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction","title":"fluid.utils.worker.WorkerFunction","text":"
    WorkerFunction(run_function, heartbeat=0, name='')\n

    Bases: StoppingWorker

    Source code in fluid/utils/worker.py
    def __init__(\n    self,\n    run_function: Callable[[], Awaitable[None]],\n    heartbeat: float | int = 0,\n    name: str = \"\",\n) -> None:\n    super().__init__(name=name)\n    self._run_function = run_function\n    self._heartbeat = heartbeat\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.worker_name","title":"worker_name property","text":"
    worker_name\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return {\"stopping\": self.is_stopping(), \"running\": self.is_running()}\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.run","title":"run async","text":"
    run()\n
    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    with self.start_running():\n        while not self.is_stopping():\n            await self._run_function()\n            await asyncio.sleep(self._heartbeat)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers","title":"fluid.utils.worker.Workers","text":"
    Workers(\n    *workers,\n    name=\"\",\n    stopping_grace_period=STOPPING_GRACE_PERIOD\n)\n

    Bases: MultipleWorkers

    A worker managing several workers

    Source code in fluid/utils/worker.py
    def __init__(\n    self,\n    *workers: Worker,\n    name: str = \"\",\n    stopping_grace_period: int = settings.STOPPING_GRACE_PERIOD,\n) -> None:\n    super().__init__(\n        *workers, name=name, stopping_grace_period=stopping_grace_period\n    )\n    self._workers_task: asyncio.Task | None = None\n    self._delayed_callbacks: list[\n        tuple[Callable[[], None], float, float, float]\n    ] = []\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.worker_name","title":"worker_name property","text":"
    worker_name\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.running","title":"running property","text":"
    running\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return await self._workers.status()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._workers.gracefully_stop()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._workers.is_stopping()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.create_task","title":"create_task","text":"
    create_task(worker)\n
    Source code in fluid/utils/worker.py
    def create_task(self, worker: Worker) -> asyncio.Task:\n    return asyncio.create_task(\n        self._run_worker(worker), name=f\"{self.worker_name}-{worker.worker_name}\"\n    )\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n

    called after the workers are stopped

    Source code in fluid/utils/worker.py
    async def on_shutdown(self) -> None:\n    \"\"\"called after the workers are stopped\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.shutdown","title":"shutdown async","text":"
    shutdown()\n

    shutdown the workers

    Source code in fluid/utils/worker.py
    async def shutdown(self) -> None:\n    \"\"\"shutdown the workers\"\"\"\n    if self._has_shutdown:\n        return\n    self._has_shutdown = True\n    logger.warning(\n        \"gracefully stopping %d workers: %s\",\n        self.num_workers,\n        \", \".join(w.worker_name for w in self._workers.workers),\n    )\n    self.gracefully_stop()\n    try:\n        async with async_timeout.timeout(self._stopping_grace_period):\n            await self.wait_for_exit()\n        await self.on_shutdown()\n        return\n    except asyncio.TimeoutError:\n        logger.warning(\n            \"could not stop workers %s gracefully after %s\"\n            \" seconds - force shutdown\",\n            \", \".join(\n                task.get_name() for task in self._workers.tasks if not task.done()\n            ),\n            self._stopping_grace_period,\n        )\n    except asyncio.CancelledError:\n        pass\n    self._force_shutdown = True\n    self._workers.cancel()\n    try:\n        await self.wait_for_exit()\n    except asyncio.CancelledError:\n        pass\n    await self.on_shutdown()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.bail_out","title":"bail_out","text":"
    bail_out(reason, code=1)\n
    Source code in fluid/utils/worker.py
    def bail_out(self, reason: str, code: int = 1) -> None:\n    self.gracefully_stop()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.safe_run","title":"safe_run async","text":"
    safe_run()\n

    Context manager to run a worker safely

    Source code in fluid/utils/worker.py
    @asynccontextmanager\nasync def safe_run(self) -> AsyncGenerator:\n    \"\"\"Context manager to run a worker safely\"\"\"\n    try:\n        yield\n    except asyncio.CancelledError:\n        if self._force_shutdown:\n            # we are shutting down, this is expected\n            pass\n        raise\n    except Exception as e:\n        reason = f\"unhandled exception while running workers: {e}\"\n        logger.exception(reason)\n        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)\n    else:\n        # worker finished without error\n        # make sure we are shutting down\n        asyncio.get_event_loop().call_soon(self.bail_out, \"worker exit\", 1)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.add_workers","title":"add_workers","text":"
    add_workers(*workers)\n

    add workers to the workers

    Source code in fluid/utils/worker.py
    def add_workers(self, *workers: Worker) -> None:\n    \"\"\"add workers to the workers\"\"\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        if worker not in workers_:\n            workers_.append(worker)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.run","title":"run async","text":"
    run()\n

    run the workers

    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    \"\"\"run the workers\"\"\"\n    with self.start_running():\n        async with self.safe_run():\n            workers, _ = self._workers.workers_tasks()\n            self._workers.workers = tuple(workers)\n            self._workers.tasks = tuple(\n                self.create_task(worker) for worker in workers\n            )\n            await asyncio.gather(*self._workers.tasks)\n        await self.shutdown()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.wait_for_exit","title":"wait_for_exit async","text":"
    wait_for_exit()\n
    Source code in fluid/utils/worker.py
    async def wait_for_exit(self) -> None:\n    if self._workers_task is not None:\n        await self._workers_task\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.remove_workers","title":"remove_workers","text":"
    remove_workers(*workers)\n

    remove workers from the workers

    Source code in fluid/utils/worker.py
    def remove_workers(self, *workers: Worker) -> None:\n    \"remove workers from the workers\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        try:\n            workers_.remove(worker)\n        except ValueError:\n            pass\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.startup","title":"startup async","text":"
    startup()\n

    start the workers

    Source code in fluid/utils/worker.py
    async def startup(self) -> None:\n    \"\"\"start the workers\"\"\"\n    if self._workers_task is None:\n        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)\n        for args in self._delayed_callbacks:\n            self._delayed_callback(*args)\n        self._delayed_callbacks = []\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.register_callback","title":"register_callback","text":"
    register_callback(\n    callback, seconds, jitter=0.0, periodic=False\n)\n

    Register a callback

    The callback can be periodic or not.

    Source code in fluid/utils/worker.py
    def register_callback(\n    self,\n    callback: Callable[[], None],\n    seconds: float,\n    jitter: float = 0.0,\n    periodic: bool | float = False,\n) -> None:\n    \"\"\"Register a callback\n\n    The callback can be periodic or not.\n    \"\"\"\n    if periodic is True:\n        periodic_float = seconds\n    elif periodic is False:\n        periodic_float = 0.0\n    else:\n        periodic_float = periodic\n    if not self.running:\n        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))\n    else:\n        self._delayed_callback(callback, seconds, jitter, periodic_float)\n
    "},{"location":"tutorials/","title":"Tutorials","text":"

    The step-by-step guides, the how-to's, the recipes, and all the Aio Fluid parts you can use in your applications.

    "},{"location":"tutorials/db/","title":"Async Database","text":"

    The fluid.db module provides a simple asynchronous interface to interact with postgres databases. It is built on top of the sqlalchemy and asyncpg libraries.

    "},{"location":"tutorials/scheduler/","title":"Task Queue","text":"

    This module has a lightweight implementation of a distributed task producer (TaskScheduler) and consumer (TaskConsumer). The middleware for distributing tasks can be configured via the Broker interface. A redis broker is provided for convenience.

    "},{"location":"tutorials/scheduler/#tasks","title":"Tasks","text":"

    Tasks are standard python async functions decorated with the task decorator.

    from fluid.scheduler import task, TaskRun\n\n@task\nasync def say_hi(ctx: TaskRun):\n    return \"Hi!\"\n

    There are two types of tasks implemented

    • Simple concurrent tasks - they run concurrently with the task consumer - thy must be IO type tasks (no heavy CPU bound operations)
      from fluid.scheduler import task, TaskRun\n\n  @task\n  async def fecth_data(ctx: TaskRun):\n      # fetch data\n      data = await http_cli.get(\"https://...\")\n      data_id = await datastore_cli.stote(data)\n      # trigger another task\n      ctx.task_manager.queue(\"heavy_calculation\", data_id=data_id)\n
    • CPU bound tasks - they run on a subprocess
    from fluid.scheduler import task, TaskRun\n\n@task(cpu_bound=True)\nasync def heavy_calculation(ctx: TaskRun):\n    data = await datastore_cli.get(ctx.params[\"data_id\"])\n    # perform some heavy calculation\n    ...\n    # trigger another task\n    ctx.task_manager.queue(\"fetch_data\")\n

    Both tasks can be periodically scheduled via the schedule keyword argument:

    from datetime import timedelta\nfrom fluid.scheduler import task, TaskContext, every\n\n@task(schedule=every(timedelta(seconds=1)))\nasync def scheduled(context: TaskContext) -> str:\n    await asyncio.sleep(0.1)\n    return \"OK\"\n
    "},{"location":"tutorials/scheduler/#broker","title":"Broker","text":"

    A Task broker needs to implement three abstract methods

      @abstractmethod\n  async def queue_task(self, queued_task: QueuedTask) -> TaskRun:\n      \"\"\"Queue a task\"\"\"\n\n  @abstractmethod\n  async def get_task_run(self) -> Optional[TaskRun]:\n      \"\"\"Get a Task run from the task queue\"\"\"\n\n  @abstractmethod\n  async def queue_length(self) -> Dict[str, int]:\n      \"\"\"Length of task queues\"\"\"\n

    The library ships a Redis broker for convenience.

    from fluid.scheduler import Broker\n\nredis_broker = Broker.from_url(\"redis://localhost:6349\")\n
    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"

    Aio Fluid

    Async utilities for backend python services developed by Quantmind.

    Documentation: https://quantmind.github.io/aio-fluid

    Source Code: https://github.com/quantmind/aio-fluid

    "},{"location":"#installation","title":"Installation","text":"

    This is a simple python package you can install via pip:

    pip install aio-fluid\n

    To install all the dependencies:

    pip install aio-fluid[cli, db, http, log]\n
    this includes the following extra dependencies:

    • cli for the command line interface using click and rich
    • db for database support with asyncpg and sqlalchemy
    • http for http client support with httpx and aiohttp
    • log for JSON logging support with python-json-logger
    "},{"location":"#development","title":"Development","text":"

    You can run the examples via

    poetry run python -m examples.main\n
    "},{"location":"reference/","title":"Introduction","text":"

    Here's the reference or code API, the classes, functions, parameters, attributes, and all the aio-fluid parts you can use in your applications.

    "},{"location":"reference/dispatchers/","title":"Event Dispatchers","text":"

    A set of classes for dispatching events, they can be imported from fluid.utils.dispatcher:

    from fluid.utils.dispatcher import Dispatcher\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.BaseDispatcher","title":"fluid.utils.dispatcher.BaseDispatcher","text":"
    BaseDispatcher()\n

    Bases: Generic[MessageType, MessageHandlerType], ABC

    Source code in fluid/utils/dispatcher.py
    def __init__(self) -> None:\n    self._msg_handlers: defaultdict[str, dict[str, MessageHandlerType]] = (\n        defaultdict(\n            dict,\n        )\n    )\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.BaseDispatcher.register_handler","title":"register_handler","text":"
    register_handler(event, handler)\n
    Source code in fluid/utils/dispatcher.py
    def register_handler(\n    self,\n    event: Event | str,\n    handler: MessageHandlerType,\n) -> MessageHandlerType | None:\n    event = Event.from_string_or_event(event)\n    previous = self._msg_handlers[event.type].get(event.tag)\n    self._msg_handlers[event.type][event.tag] = handler\n    return previous\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.BaseDispatcher.unregister_handler","title":"unregister_handler","text":"
    unregister_handler(event)\n
    Source code in fluid/utils/dispatcher.py
    def unregister_handler(self, event: Event | str) -> MessageHandlerType | None:\n    event = Event.from_string_or_event(event)\n    return self._msg_handlers[event.type].pop(event.tag, None)\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.BaseDispatcher.get_handlers","title":"get_handlers","text":"
    get_handlers(message)\n
    Source code in fluid/utils/dispatcher.py
    def get_handlers(\n    self,\n    message: MessageType,\n) -> dict[str, MessageHandlerType] | None:\n    message_type = str(self.message_type(message))\n    return self._msg_handlers.get(message_type)\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.BaseDispatcher.message_type","title":"message_type abstractmethod","text":"
    message_type(message)\n

    return the message type

    Source code in fluid/utils/dispatcher.py
    @abstractmethod\ndef message_type(self, message: MessageType) -> str:\n    \"\"\"return the message type\"\"\"\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.Dispatcher","title":"fluid.utils.dispatcher.Dispatcher","text":"
    Dispatcher()\n

    Bases: BaseDispatcher[MessageType, Callable[[MessageType], None]]

    Dispatcher for sync handlers

    Source code in fluid/utils/dispatcher.py
    def __init__(self) -> None:\n    self._msg_handlers: defaultdict[str, dict[str, MessageHandlerType]] = (\n        defaultdict(\n            dict,\n        )\n    )\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.Dispatcher.register_handler","title":"register_handler","text":"
    register_handler(event, handler)\n
    Source code in fluid/utils/dispatcher.py
    def register_handler(\n    self,\n    event: Event | str,\n    handler: MessageHandlerType,\n) -> MessageHandlerType | None:\n    event = Event.from_string_or_event(event)\n    previous = self._msg_handlers[event.type].get(event.tag)\n    self._msg_handlers[event.type][event.tag] = handler\n    return previous\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.Dispatcher.unregister_handler","title":"unregister_handler","text":"
    unregister_handler(event)\n
    Source code in fluid/utils/dispatcher.py
    def unregister_handler(self, event: Event | str) -> MessageHandlerType | None:\n    event = Event.from_string_or_event(event)\n    return self._msg_handlers[event.type].pop(event.tag, None)\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.Dispatcher.get_handlers","title":"get_handlers","text":"
    get_handlers(message)\n
    Source code in fluid/utils/dispatcher.py
    def get_handlers(\n    self,\n    message: MessageType,\n) -> dict[str, MessageHandlerType] | None:\n    message_type = str(self.message_type(message))\n    return self._msg_handlers.get(message_type)\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.Dispatcher.message_type","title":"message_type abstractmethod","text":"
    message_type(message)\n

    return the message type

    Source code in fluid/utils/dispatcher.py
    @abstractmethod\ndef message_type(self, message: MessageType) -> str:\n    \"\"\"return the message type\"\"\"\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.Dispatcher.dispatch","title":"dispatch","text":"
    dispatch(message)\n

    dispatch the message

    Source code in fluid/utils/dispatcher.py
    def dispatch(self, message: MessageType) -> int:\n    \"\"\"dispatch the message\"\"\"\n    handlers = self.get_handlers(message)\n    if handlers:\n        for handler in handlers.values():\n            handler(message)\n    return len(handlers or ())\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.AsyncDispatcher","title":"fluid.utils.dispatcher.AsyncDispatcher","text":"
    AsyncDispatcher()\n

    Bases: BaseDispatcher[MessageType, Callable[[MessageType], Awaitable[None]]]

    Dispatcher for async handlers

    Source code in fluid/utils/dispatcher.py
    def __init__(self) -> None:\n    self._msg_handlers: defaultdict[str, dict[str, MessageHandlerType]] = (\n        defaultdict(\n            dict,\n        )\n    )\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.AsyncDispatcher.register_handler","title":"register_handler","text":"
    register_handler(event, handler)\n
    Source code in fluid/utils/dispatcher.py
    def register_handler(\n    self,\n    event: Event | str,\n    handler: MessageHandlerType,\n) -> MessageHandlerType | None:\n    event = Event.from_string_or_event(event)\n    previous = self._msg_handlers[event.type].get(event.tag)\n    self._msg_handlers[event.type][event.tag] = handler\n    return previous\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.AsyncDispatcher.unregister_handler","title":"unregister_handler","text":"
    unregister_handler(event)\n
    Source code in fluid/utils/dispatcher.py
    def unregister_handler(self, event: Event | str) -> MessageHandlerType | None:\n    event = Event.from_string_or_event(event)\n    return self._msg_handlers[event.type].pop(event.tag, None)\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.AsyncDispatcher.get_handlers","title":"get_handlers","text":"
    get_handlers(message)\n
    Source code in fluid/utils/dispatcher.py
    def get_handlers(\n    self,\n    message: MessageType,\n) -> dict[str, MessageHandlerType] | None:\n    message_type = str(self.message_type(message))\n    return self._msg_handlers.get(message_type)\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.AsyncDispatcher.message_type","title":"message_type abstractmethod","text":"
    message_type(message)\n

    return the message type

    Source code in fluid/utils/dispatcher.py
    @abstractmethod\ndef message_type(self, message: MessageType) -> str:\n    \"\"\"return the message type\"\"\"\n
    "},{"location":"reference/dispatchers/#fluid.utils.dispatcher.AsyncDispatcher.dispatch","title":"dispatch async","text":"
    dispatch(message)\n

    Dispatch the message and wait for all handlers to complete

    Source code in fluid/utils/dispatcher.py
    async def dispatch(self, message: MessageType) -> int:\n    \"\"\"Dispatch the message and wait for all handlers to complete\"\"\"\n    handlers = self.get_handlers(message)\n    if handlers:\n        await asyncio.gather(*[handler(message) for handler in handlers.values()])\n    return len(handlers or ())\n
    "},{"location":"reference/task_broker/","title":"Task Broker","text":"

    It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskBroker\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker","title":"fluid.scheduler.TaskBroker","text":"
    TaskBroker(url)\n

    Bases: ABC

    Source code in fluid/scheduler/broker.py
    def __init__(self, url: URL) -> None:\n    self.url: URL = url\n    self.registry: TaskRegistry = TaskRegistry()\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.url","title":"url instance-attribute","text":"
    url = url\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.registry","title":"registry instance-attribute","text":"
    registry = TaskRegistry()\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.task_queue_names","title":"task_queue_names abstractmethod property","text":"
    task_queue_names\n

    Names of the task queues

    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.queue_task","title":"queue_task abstractmethod async","text":"
    queue_task(task_run)\n

    Queue a task

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def queue_task(self, task_run: TaskRun) -> None:\n    \"\"\"Queue a task\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.get_task_run","title":"get_task_run abstractmethod async","text":"
    get_task_run(task_manager)\n

    Get a Task run from the task queue

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def get_task_run(self, task_manager: TaskManager) -> TaskRun | None:\n    \"\"\"Get a Task run from the task queue\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.queue_length","title":"queue_length abstractmethod async","text":"
    queue_length()\n

    Length of task queues

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def queue_length(self) -> dict[str, int]:\n    \"\"\"Length of task queues\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.get_tasks_info","title":"get_tasks_info abstractmethod async","text":"
    get_tasks_info(*task_names)\n

    List of TaskInfo objects

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def get_tasks_info(self, *task_names: str) -> list[TaskInfo]:\n    \"\"\"List of TaskInfo objects\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.update_task","title":"update_task abstractmethod async","text":"
    update_task(task, params)\n

    Update a task dynamic parameters

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def update_task(self, task: Task, params: dict[str, Any]) -> TaskInfo:\n    \"\"\"Update a task dynamic parameters\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.close","title":"close abstractmethod async","text":"
    close()\n

    Close the broker on shutdown

    Source code in fluid/scheduler/broker.py
    @abstractmethod\nasync def close(self) -> None:\n    \"\"\"Close the broker on shutdown\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.lock","title":"lock abstractmethod","text":"
    lock(name, timeout=None)\n

    Create a lock

    Source code in fluid/scheduler/broker.py
    @abstractmethod\ndef lock(self, name: str, timeout: float | None = None) -> Lock:\n    \"\"\"Create a lock\"\"\"\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.new_uuid","title":"new_uuid","text":"
    new_uuid()\n
    Source code in fluid/scheduler/broker.py
    def new_uuid(self) -> str:\n    return uuid4().hex\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.filter_tasks","title":"filter_tasks async","text":"
    filter_tasks(scheduled=None, enabled=None)\n
    Source code in fluid/scheduler/broker.py
    async def filter_tasks(\n    self,\n    scheduled: bool | None = None,\n    enabled: bool | None = None,\n) -> list[Task]:\n    task_info = await self.get_tasks_info()\n    task_map = {info.name: info for info in task_info}\n    tasks = []\n    for task in self.registry.values():\n        if scheduled is not None and bool(task.schedule) is not scheduled:\n            continue\n        if enabled is not None and task_map[task.name].enabled is not enabled:\n            continue\n        tasks.append(task)\n    return tasks\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.task_from_registry","title":"task_from_registry","text":"
    task_from_registry(task)\n
    Source code in fluid/scheduler/broker.py
    def task_from_registry(self, task: str | Task) -> Task:\n    if isinstance(task, Task):\n        self.register_task(task)\n        return task\n    else:\n        if task_ := self.registry.get(task):\n            return task_\n        raise UnknownTaskError(task)\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.register_task","title":"register_task","text":"
    register_task(task)\n
    Source code in fluid/scheduler/broker.py
    def register_task(self, task: Task) -> None:\n    self.registry[task.name] = task\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.enable_task","title":"enable_task async","text":"
    enable_task(task_name, enable=True)\n

    Enable or disable a registered task

    Source code in fluid/scheduler/broker.py
    async def enable_task(self, task_name: str, enable: bool = True) -> TaskInfo:\n    \"\"\"Enable or disable a registered task\"\"\"\n    task = self.registry.get(task_name)\n    if not task:\n        raise UnknownTaskError(task_name)\n    return await self.update_task(task, dict(enabled=enable))\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.from_url","title":"from_url classmethod","text":"
    from_url(url='')\n
    Source code in fluid/scheduler/broker.py
    @classmethod\ndef from_url(cls, url: str = \"\") -> TaskBroker:\n    p = URL(url or broker_url_from_env())\n    if factory := _brokers.get(p.scheme):\n        return factory(p)\n    raise RuntimeError(f\"Invalid broker {p}\")\n
    "},{"location":"reference/task_broker/#fluid.scheduler.TaskBroker.register_broker","title":"register_broker classmethod","text":"
    register_broker(name, factory)\n
    Source code in fluid/scheduler/broker.py
    @classmethod\ndef register_broker(cls, name: str, factory: type[TaskBroker]) -> None:\n    _brokers[name] = factory\n
    "},{"location":"reference/task_manager/","title":"Task Manager","text":"

    It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskManager\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager","title":"fluid.scheduler.TaskManager","text":"
    TaskManager(**kwargs)\n

    The task manager is the main entry point for managing tasks

    Source code in fluid/scheduler/consumer.py
    def __init__(self, **kwargs: Any) -> None:\n    self.state: dict[str, Any] = {}\n    self.config: TaskManagerConfig = TaskManagerConfig(**kwargs)\n    self.dispatcher: Annotated[\n        TaskDispatcher,\n        Doc(\n            \"\"\"\n            A dispatcher of task run events.\n\n            Register handlers to listen for task run events.\n            \"\"\"\n        ),\n    ] = TaskDispatcher()\n    self.broker = TaskBroker.from_url(self.config.broker_url)\n    self._stack = AsyncExitStack()\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.state","title":"state instance-attribute","text":"
    state = {}\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.config","title":"config instance-attribute","text":"
    config = TaskManagerConfig(**kwargs)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.dispatcher","title":"dispatcher instance-attribute","text":"
    dispatcher = TaskDispatcher()\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.broker","title":"broker instance-attribute","text":"
    broker = from_url(broker_url)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.registry","title":"registry property","text":"
    registry\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.type","title":"type property","text":"
    type\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.enter_async_context","title":"enter_async_context async","text":"
    enter_async_context(cm)\n
    Source code in fluid/scheduler/consumer.py
    async def enter_async_context(self, cm: Any) -> Any:\n    return await self._stack.enter_async_context(cm)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.execute","title":"execute async","text":"
    execute(task, **params)\n

    Execute a task and wait for it to finish

    Source code in fluid/scheduler/consumer.py
    async def execute(self, task: Task | str, **params: Any) -> TaskRun:\n    \"\"\"Execute a task and wait for it to finish\"\"\"\n    task_run = self.create_task_run(task, **params)\n    await task_run.execute()\n    return task_run\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n
    Source code in fluid/scheduler/consumer.py
    async def on_shutdown(self) -> None:\n    await self.broker.close()\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.execute_sync","title":"execute_sync","text":"
    execute_sync(task, **params)\n
    Source code in fluid/scheduler/consumer.py
    def execute_sync(self, task: Task | str, **params: Any) -> TaskRun:\n    return asyncio.run(self._execute_and_exit(task, **params))\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.register_task","title":"register_task","text":"
    register_task(task)\n

    Register a task with the task manager

    Only tasks registered can be executed by a task manager

    Source code in fluid/scheduler/consumer.py
    def register_task(self, task: Task) -> None:\n    \"\"\"Register a task with the task manager\n\n    Only tasks registered can be executed by a task manager\n    \"\"\"\n    self.broker.register_task(task)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.queue","title":"queue async","text":"
    queue(task, priority=None, **params)\n

    Queue a task for execution

    This methods fires two events:

    • queue: when the task is about to be queued
    • queued: after the task is queued
    Source code in fluid/scheduler/consumer.py
    async def queue(\n    self,\n    task: str | Task,\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Queue a task for execution\n\n    This methods fires two events:\n\n    - queue: when the task is about to be queued\n    - queued: after the task is queued\n    \"\"\"\n    task_run = self.create_task_run(task, priority=priority, **params)\n    self.dispatcher.dispatch(task_run)\n    task_run.set_state(TaskState.queued)\n    await self.broker.queue_task(task_run)\n    return task_run\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.create_task_run","title":"create_task_run","text":"
    create_task_run(task, run_id='', priority=None, **params)\n

    Create a TaskRun in init state

    Source code in fluid/scheduler/consumer.py
    def create_task_run(\n    self,\n    task: str | Task,\n    run_id: str = \"\",\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Create a TaskRun in `init` state\"\"\"\n    if isinstance(task, str):\n        task = self.broker.task_from_registry(task)\n    run_id = run_id or self.broker.new_uuid()\n    return TaskRun(\n        id=run_id,\n        task=task,\n        priority=priority or task.priority,\n        params=params,\n        task_manager=self,\n    )\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.register_from_module","title":"register_from_module","text":"
    register_from_module(module)\n
    Source code in fluid/scheduler/consumer.py
    def register_from_module(self, module: Any) -> None:\n    for name in dir(module):\n        if name.startswith(\"_\"):\n            continue\n        if isinstance(obj := getattr(module, name), Task):\n            self.register_task(obj)\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.register_async_handler","title":"register_async_handler","text":"
    register_async_handler(event, handler)\n

    Register an async handler for a given event

    This method is a no op for a TaskManager that is not a worker

    Source code in fluid/scheduler/consumer.py
    def register_async_handler(self, event: str, handler: AsyncHandler) -> None:\n    \"\"\"Register an async handler for a given event\n\n    This method is a no op for a TaskManager that is not a worker\n    \"\"\"\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.unregister_async_handler","title":"unregister_async_handler","text":"
    unregister_async_handler(event)\n

    Unregister an async handler for a given event

    This method is a no op for a TaskManager that is not a worker

    Source code in fluid/scheduler/consumer.py
    def unregister_async_handler(self, event: Event | str) -> AsyncHandler | None:\n    \"\"\"Unregister an async handler for a given event\n\n    This method is a no op for a TaskManager that is not a worker\n    \"\"\"\n    return None\n
    "},{"location":"reference/task_manager/#fluid.scheduler.TaskManager.cli","title":"cli","text":"
    cli(**kwargs)\n

    Create the task manager command line interface

    Source code in fluid/scheduler/consumer.py
    def cli(self, **kwargs: Any) -> Any:\n    \"\"\"Create the task manager command line interface\"\"\"\n    try:\n        from fluid.scheduler.cli import TaskManagerCLI\n    except ImportError:\n        raise ImportError(\n            \"TaskManagerCLI is not available - \"\n            \"install with `pip install aio-fluid[cli]`\"\n        ) from None\n    return TaskManagerCLI(self, **kwargs)\n
    "},{"location":"reference/task_run/","title":"Task Run","text":"

    It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskRun\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun","title":"fluid.scheduler.TaskRun","text":"

    Bases: BaseModel

    A TaskRun contains all the data generated by a Task run

    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.id","title":"id instance-attribute","text":"
    id\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.task","title":"task instance-attribute","text":"
    task\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.priority","title":"priority instance-attribute","text":"
    priority\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.params","title":"params instance-attribute","text":"
    params\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.state","title":"state class-attribute instance-attribute","text":"
    state = init\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.task_manager","title":"task_manager class-attribute instance-attribute","text":"
    task_manager = Field(exclude=True, repr=False)\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.queued","title":"queued class-attribute instance-attribute","text":"
    queued = None\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.start","title":"start class-attribute instance-attribute","text":"
    start = None\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.end","title":"end class-attribute instance-attribute","text":"
    end = None\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.logger","title":"logger property","text":"
    logger\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.in_queue","title":"in_queue property","text":"
    in_queue\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.duration","title":"duration property","text":"
    duration\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.duration_ms","title":"duration_ms property","text":"
    duration_ms\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.total","title":"total property","text":"
    total\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.name","title":"name property","text":"
    name\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.name_id","title":"name_id property","text":"
    name_id\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.is_done","title":"is_done property","text":"
    is_done\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.is_failure","title":"is_failure property","text":"
    is_failure\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.execute","title":"execute async","text":"
    execute()\n
    Source code in fluid/scheduler/models.py
    async def execute(self) -> None:\n    try:\n        self.set_state(TaskState.running)\n        await self.task.executor(self)\n    except Exception:\n        self.set_state(TaskState.failure)\n        raise\n    else:\n        self.set_state(TaskState.success)\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.serialize_task","title":"serialize_task","text":"
    serialize_task(task, _info)\n
    Source code in fluid/scheduler/models.py
    @field_serializer(\"task\")\ndef serialize_task(self, task: Task, _info: Any) -> str:\n    return task.name\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.params_dump_json","title":"params_dump_json","text":"
    params_dump_json()\n
    Source code in fluid/scheduler/models.py
    def params_dump_json(self) -> str:\n    return self.task.params_dump_json(self.params)\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.set_state","title":"set_state","text":"
    set_state(state, state_time=None)\n
    Source code in fluid/scheduler/models.py
    def set_state(\n    self,\n    state: TaskState,\n    state_time: datetime | None = None,\n) -> None:\n    if self.state == state:\n        return\n    state_time = as_utc(state_time)\n    match (self.state, state):\n        case (TaskState.init, TaskState.queued):\n            self.queued = state_time\n            self.state = state\n            self._dispatch()\n        case (TaskState.init, _):\n            self.set_state(TaskState.queued, state_time)\n            self.set_state(state, state_time)\n        case (TaskState.queued, TaskState.running):\n            self.start = state_time\n            self.state = state\n            self._dispatch()\n        case (\n            TaskState.queued,\n            TaskState.success\n            | TaskState.aborted\n            | TaskState.rate_limited\n            | TaskState.failure,\n        ):\n            self.set_state(TaskState.running, state_time)\n            self.set_state(state, state_time)\n        case (\n            TaskState.running,\n            TaskState.success\n            | TaskState.aborted\n            | TaskState.rate_limited\n            | TaskState.failure,\n        ):\n            self.end = state_time\n            self.state = state\n            self._dispatch()\n        case _:\n            raise TaskRunError(f\"invalid state transition {self.state} -> {state}\")\n
    "},{"location":"reference/task_run/#fluid.scheduler.TaskRun.lock","title":"lock","text":"
    lock(timeout)\n
    Source code in fluid/scheduler/models.py
    def lock(self, timeout: float | None) -> Lock:\n    return self.task_manager.broker.lock(self.name, timeout=timeout)\n
    "},{"location":"reference/tast_consumer/","title":"Task Consumer","text":"

    The task consumer is a TaskManager which is also a Workers that consumes tasks from the task queue and executes them. It can be imported from fluid.scheduler:

    from fastapi.scheduler import TaskConsumer\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer","title":"fluid.scheduler.TaskConsumer","text":"
    TaskConsumer(**config)\n

    Bases: TaskManager, Workers

    The Task Consumer is a Task Manager responsible for consuming tasks from a task queue

    Source code in fluid/scheduler/consumer.py
    def __init__(self, **config: Any) -> None:\n    super().__init__(**config)\n    Workers.__init__(self)\n    self._async_dispatcher_worker = AsyncConsumer(AsyncTaskDispatcher())\n    self._concurrent_tasks: dict[str, dict[str, TaskRun]] = defaultdict(dict)\n    self._task_to_queue: deque[str | Task] = deque()\n    self._priority_task_run_queue: deque[TaskRun] = deque()\n    self._queue_tasks_worker = WorkerFunction(\n        self._queue_task, name=\"queue-task-worker\"\n    )\n    self.add_workers(self._queue_tasks_worker)\n    self.add_workers(self._async_dispatcher_worker)\n    for i in range(self.config.max_concurrent_tasks):\n        worker_name = f\"task-worker-{i+1}\"\n        self.add_workers(\n            WorkerFunction(\n                partial(self._consume_tasks, worker_name), name=worker_name\n            )\n        )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.running","title":"running property","text":"
    running\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.state","title":"state instance-attribute","text":"
    state = {}\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.config","title":"config instance-attribute","text":"
    config = TaskManagerConfig(**kwargs)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.dispatcher","title":"dispatcher instance-attribute","text":"
    dispatcher = TaskDispatcher()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.broker","title":"broker instance-attribute","text":"
    broker = from_url(broker_url)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.registry","title":"registry property","text":"
    registry\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.type","title":"type property","text":"
    type\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.num_concurrent_tasks","title":"num_concurrent_tasks property","text":"
    num_concurrent_tasks\n

    The number of concurrent_tasks running in the consumer

    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return await self._workers.status()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._workers.gracefully_stop()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._workers.is_stopping()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.run","title":"run async","text":"
    run()\n

    run the workers

    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    \"\"\"run the workers\"\"\"\n    with self.start_running():\n        async with self.safe_run():\n            workers, _ = self._workers.workers_tasks()\n            self._workers.workers = tuple(workers)\n            self._workers.tasks = tuple(\n                self.create_task(worker) for worker in workers\n            )\n            await asyncio.gather(*self._workers.tasks)\n        await self.shutdown()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.add_workers","title":"add_workers","text":"
    add_workers(*workers)\n

    add workers to the workers

    Source code in fluid/utils/worker.py
    def add_workers(self, *workers: Worker) -> None:\n    \"\"\"add workers to the workers\"\"\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        if worker not in workers_:\n            workers_.append(worker)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.wait_for_exit","title":"wait_for_exit async","text":"
    wait_for_exit()\n
    Source code in fluid/utils/worker.py
    async def wait_for_exit(self) -> None:\n    if self._workers_task is not None:\n        await self._workers_task\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.create_task","title":"create_task","text":"
    create_task(worker)\n
    Source code in fluid/utils/worker.py
    def create_task(self, worker: Worker) -> asyncio.Task:\n    return asyncio.create_task(\n        self._run_worker(worker), name=f\"{self.worker_name}-{worker.worker_name}\"\n    )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n
    Source code in fluid/scheduler/consumer.py
    async def on_shutdown(self) -> None:\n    await self.broker.close()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.shutdown","title":"shutdown async","text":"
    shutdown()\n

    shutdown the workers

    Source code in fluid/utils/worker.py
    async def shutdown(self) -> None:\n    \"\"\"shutdown the workers\"\"\"\n    if self._has_shutdown:\n        return\n    self._has_shutdown = True\n    logger.warning(\n        \"gracefully stopping %d workers: %s\",\n        self.num_workers,\n        \", \".join(w.worker_name for w in self._workers.workers),\n    )\n    self.gracefully_stop()\n    try:\n        async with async_timeout.timeout(self._stopping_grace_period):\n            await self.wait_for_exit()\n        await self.on_shutdown()\n        return\n    except asyncio.TimeoutError:\n        logger.warning(\n            \"could not stop workers %s gracefully after %s\"\n            \" seconds - force shutdown\",\n            \", \".join(\n                task.get_name() for task in self._workers.tasks if not task.done()\n            ),\n            self._stopping_grace_period,\n        )\n    except asyncio.CancelledError:\n        pass\n    self._force_shutdown = True\n    self._workers.cancel()\n    try:\n        await self.wait_for_exit()\n    except asyncio.CancelledError:\n        pass\n    await self.on_shutdown()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.bail_out","title":"bail_out","text":"
    bail_out(reason, code=1)\n
    Source code in fluid/utils/worker.py
    def bail_out(self, reason: str, code: int = 1) -> None:\n    self.gracefully_stop()\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.safe_run","title":"safe_run async","text":"
    safe_run()\n

    Context manager to run a worker safely

    Source code in fluid/utils/worker.py
    @asynccontextmanager\nasync def safe_run(self) -> AsyncGenerator:\n    \"\"\"Context manager to run a worker safely\"\"\"\n    try:\n        yield\n    except asyncio.CancelledError:\n        if self._force_shutdown:\n            # we are shutting down, this is expected\n            pass\n        raise\n    except Exception as e:\n        reason = f\"unhandled exception while running workers: {e}\"\n        logger.exception(reason)\n        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)\n    else:\n        # worker finished without error\n        # make sure we are shutting down\n        asyncio.get_event_loop().call_soon(self.bail_out, \"worker exit\", 1)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.remove_workers","title":"remove_workers","text":"
    remove_workers(*workers)\n

    remove workers from the workers

    Source code in fluid/utils/worker.py
    def remove_workers(self, *workers: Worker) -> None:\n    \"remove workers from the workers\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        try:\n            workers_.remove(worker)\n        except ValueError:\n            pass\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.startup","title":"startup async","text":"
    startup()\n

    start the workers

    Source code in fluid/utils/worker.py
    async def startup(self) -> None:\n    \"\"\"start the workers\"\"\"\n    if self._workers_task is None:\n        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)\n        for args in self._delayed_callbacks:\n            self._delayed_callback(*args)\n        self._delayed_callbacks = []\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_callback","title":"register_callback","text":"
    register_callback(\n    callback, seconds, jitter=0.0, periodic=False\n)\n

    Register a callback

    The callback can be periodic or not.

    Source code in fluid/utils/worker.py
    def register_callback(\n    self,\n    callback: Callable[[], None],\n    seconds: float,\n    jitter: float = 0.0,\n    periodic: bool | float = False,\n) -> None:\n    \"\"\"Register a callback\n\n    The callback can be periodic or not.\n    \"\"\"\n    if periodic is True:\n        periodic_float = seconds\n    elif periodic is False:\n        periodic_float = 0.0\n    else:\n        periodic_float = periodic\n    if not self.running:\n        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))\n    else:\n        self._delayed_callback(callback, seconds, jitter, periodic_float)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.enter_async_context","title":"enter_async_context async","text":"
    enter_async_context(cm)\n
    Source code in fluid/scheduler/consumer.py
    async def enter_async_context(self, cm: Any) -> Any:\n    return await self._stack.enter_async_context(cm)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.execute","title":"execute async","text":"
    execute(task, **params)\n

    Execute a task and wait for it to finish

    Source code in fluid/scheduler/consumer.py
    async def execute(self, task: Task | str, **params: Any) -> TaskRun:\n    \"\"\"Execute a task and wait for it to finish\"\"\"\n    task_run = self.create_task_run(task, **params)\n    await task_run.execute()\n    return task_run\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.execute_sync","title":"execute_sync","text":"
    execute_sync(task, **params)\n
    Source code in fluid/scheduler/consumer.py
    def execute_sync(self, task: Task | str, **params: Any) -> TaskRun:\n    return asyncio.run(self._execute_and_exit(task, **params))\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_task","title":"register_task","text":"
    register_task(task)\n

    Register a task with the task manager

    Only tasks registered can be executed by a task manager

    Source code in fluid/scheduler/consumer.py
    def register_task(self, task: Task) -> None:\n    \"\"\"Register a task with the task manager\n\n    Only tasks registered can be executed by a task manager\n    \"\"\"\n    self.broker.register_task(task)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.queue","title":"queue async","text":"
    queue(task, priority=None, **params)\n

    Queue a task for execution

    This methods fires two events:

    • queue: when the task is about to be queued
    • queued: after the task is queued
    Source code in fluid/scheduler/consumer.py
    async def queue(\n    self,\n    task: str | Task,\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Queue a task for execution\n\n    This methods fires two events:\n\n    - queue: when the task is about to be queued\n    - queued: after the task is queued\n    \"\"\"\n    task_run = self.create_task_run(task, priority=priority, **params)\n    self.dispatcher.dispatch(task_run)\n    task_run.set_state(TaskState.queued)\n    await self.broker.queue_task(task_run)\n    return task_run\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.create_task_run","title":"create_task_run","text":"
    create_task_run(task, run_id='', priority=None, **params)\n

    Create a TaskRun in init state

    Source code in fluid/scheduler/consumer.py
    def create_task_run(\n    self,\n    task: str | Task,\n    run_id: str = \"\",\n    priority: TaskPriority | None = None,\n    **params: Any,\n) -> TaskRun:\n    \"\"\"Create a TaskRun in `init` state\"\"\"\n    if isinstance(task, str):\n        task = self.broker.task_from_registry(task)\n    run_id = run_id or self.broker.new_uuid()\n    return TaskRun(\n        id=run_id,\n        task=task,\n        priority=priority or task.priority,\n        params=params,\n        task_manager=self,\n    )\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_from_module","title":"register_from_module","text":"
    register_from_module(module)\n
    Source code in fluid/scheduler/consumer.py
    def register_from_module(self, module: Any) -> None:\n    for name in dir(module):\n        if name.startswith(\"_\"):\n            continue\n        if isinstance(obj := getattr(module, name), Task):\n            self.register_task(obj)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.cli","title":"cli","text":"
    cli(**kwargs)\n

    Create the task manager command line interface

    Source code in fluid/scheduler/consumer.py
    def cli(self, **kwargs: Any) -> Any:\n    \"\"\"Create the task manager command line interface\"\"\"\n    try:\n        from fluid.scheduler.cli import TaskManagerCLI\n    except ImportError:\n        raise ImportError(\n            \"TaskManagerCLI is not available - \"\n            \"install with `pip install aio-fluid[cli]`\"\n        ) from None\n    return TaskManagerCLI(self, **kwargs)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.sync_queue","title":"sync_queue","text":"
    sync_queue(task)\n
    Source code in fluid/scheduler/consumer.py
    def sync_queue(self, task: str | Task) -> None:\n    self._task_to_queue.appendleft(task)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.sync_priority_queue","title":"sync_priority_queue","text":"
    sync_priority_queue(task)\n
    Source code in fluid/scheduler/consumer.py
    def sync_priority_queue(self, task: str | Task) -> None:\n    self._priority_task_run_queue.appendleft(self.create_task_run(task))\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.num_concurrent_tasks_for","title":"num_concurrent_tasks_for","text":"
    num_concurrent_tasks_for(task_name)\n

    The number of concurrent tasks for a given task_name

    Source code in fluid/scheduler/consumer.py
    def num_concurrent_tasks_for(self, task_name: str) -> int:\n    \"\"\"The number of concurrent tasks for a given task_name\"\"\"\n    return len(self._concurrent_tasks[task_name])\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.queue_and_wait","title":"queue_and_wait async","text":"
    queue_and_wait(task, *, timeout=2, **params)\n

    Queue a task and wait for it to finish

    Source code in fluid/scheduler/consumer.py
    async def queue_and_wait(\n    self, task: str, *, timeout: int = 2, **params: Any\n) -> TaskRun:\n    \"\"\"Queue a task and wait for it to finish\"\"\"\n    with TaskRunWaiter(self) as waiter:\n        return await waiter.wait(await self.queue(task, **params), timeout=timeout)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.register_async_handler","title":"register_async_handler","text":"
    register_async_handler(event, handler)\n
    Source code in fluid/scheduler/consumer.py
    def register_async_handler(self, event: Event | str, handler: AsyncHandler) -> None:\n    event = Event.from_string_or_event(event)\n    self.dispatcher.register_handler(\n        f\"{event.type}.async_dispatch\",\n        self._async_dispatcher_worker.send,\n    )\n    self._async_dispatcher_worker.dispatcher.register_handler(event, handler)\n
    "},{"location":"reference/tast_consumer/#fluid.scheduler.TaskConsumer.unregister_async_handler","title":"unregister_async_handler","text":"
    unregister_async_handler(event)\n
    Source code in fluid/scheduler/consumer.py
    def unregister_async_handler(self, event: Event | str) -> AsyncHandler | None:\n    return self._async_dispatcher_worker.dispatcher.unregister_handler(event)\n
    "},{"location":"reference/workers/","title":"Workers","text":"

    Workers are the main building block for asynchronous programming with aio-fluid. They are responsible for running tasks and managing their lifecycle. There are several worker classes which can be imported from fluid.utils.worker:

    from fastapi.utils.worker import StoppingWorker\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker","title":"fluid.utils.worker.Worker","text":"
    Worker(name='')\n

    Bases: ABC

    The base class of a worker that can be run

    Source code in fluid/utils/worker.py
    def __init__(self, name: str = \"\") -> None:\n    self._name: str = name or underscore(type(self).__name__)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.Worker.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.Worker.status","title":"status abstractmethod async","text":"
    status()\n

    Get the status of the worker.

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def status(self) -> dict:\n    \"\"\"\n    Get the status of the worker.\n    \"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.gracefully_stop","title":"gracefully_stop abstractmethod","text":"
    gracefully_stop()\n

    gracefully stop the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef gracefully_stop(self) -> None:\n    \"gracefully stop the worker\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.is_running","title":"is_running abstractmethod","text":"
    is_running()\n

    Is the worker running?

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef is_running(self) -> bool:\n    \"\"\"Is the worker running?\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.is_stopping","title":"is_stopping abstractmethod","text":"
    is_stopping()\n

    Is the worker stopping?

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef is_stopping(self) -> bool:\n    \"\"\"Is the worker stopping?\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Worker.run","title":"run abstractmethod async","text":"
    run()\n

    run the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def run(self) -> None:\n    \"\"\"run the worker\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker","title":"fluid.utils.worker.RunningWorker","text":"
    RunningWorker(name='')\n

    Bases: Worker

    A Worker that can be started

    Source code in fluid/utils/worker.py
    def __init__(self, name: str = \"\") -> None:\n    super().__init__(name)\n    self._running: bool = False\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.status","title":"status abstractmethod async","text":"
    status()\n

    Get the status of the worker.

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def status(self) -> dict:\n    \"\"\"\n    Get the status of the worker.\n    \"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.gracefully_stop","title":"gracefully_stop abstractmethod","text":"
    gracefully_stop()\n

    gracefully stop the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef gracefully_stop(self) -> None:\n    \"gracefully stop the worker\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.is_stopping","title":"is_stopping abstractmethod","text":"
    is_stopping()\n

    Is the worker stopping?

    Source code in fluid/utils/worker.py
    @abstractmethod\ndef is_stopping(self) -> bool:\n    \"\"\"Is the worker stopping?\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.run","title":"run abstractmethod async","text":"
    run()\n

    run the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def run(self) -> None:\n    \"\"\"run the worker\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.RunningWorker.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker","title":"fluid.utils.worker.StoppingWorker","text":"
    StoppingWorker(name='')\n

    Bases: RunningWorker

    A Worker that can be stopped

    Source code in fluid/utils/worker.py
    def __init__(self, name: str = \"\") -> None:\n    super().__init__(name)\n    self._stopping: bool = False\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.run","title":"run abstractmethod async","text":"
    run()\n

    run the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def run(self) -> None:\n    \"\"\"run the worker\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.StoppingWorker.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return {\"stopping\": self.is_stopping(), \"running\": self.is_running()}\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction","title":"fluid.utils.worker.WorkerFunction","text":"
    WorkerFunction(run_function, heartbeat=0, name='')\n

    Bases: StoppingWorker

    A Worker that runs a coroutine function

    Source code in fluid/utils/worker.py
    def __init__(\n    self,\n    run_function: Callable[[], Awaitable[None]],\n    heartbeat: float | int = 0,\n    name: str = \"\",\n) -> None:\n    super().__init__(name=name)\n    self._run_function = run_function\n    self._heartbeat = heartbeat\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return {\"stopping\": self.is_stopping(), \"running\": self.is_running()}\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.WorkerFunction.run","title":"run async","text":"
    run()\n
    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    with self.start_running():\n        while not self.is_stopping():\n            await self._run_function()\n            await asyncio.sleep(self._heartbeat)\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer","title":"fluid.utils.worker.QueueConsumer","text":"
    QueueConsumer(name='')\n

    Bases: StoppingWorker, MessageProducer[MessageType]

    A Worker that can receive messages

    This worker can receive messages but not consume them.

    Source code in fluid/utils/worker.py
    def __init__(self, name: str = \"\") -> None:\n    super().__init__(name=name)\n    self._queue: asyncio.Queue[MessageType | None] = asyncio.Queue()\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.run","title":"run abstractmethod async","text":"
    run()\n

    run the worker

    Source code in fluid/utils/worker.py
    @abstractmethod\nasync def run(self) -> None:\n    \"\"\"run the worker\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.get_message","title":"get_message async","text":"
    get_message(timeout=0.5)\n
    Source code in fluid/utils/worker.py
    async def get_message(self, timeout: float = 0.5) -> MessageType | None:\n    try:\n        async with asyncio.timeout(timeout):\n            return await self._queue.get()\n    except asyncio.TimeoutError:\n        return None\n    except (asyncio.CancelledError, RuntimeError):\n        if not self.is_stopping():\n            raise\n    return None\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.queue_size","title":"queue_size","text":"
    queue_size()\n
    Source code in fluid/utils/worker.py
    def queue_size(self) -> int:\n    return self._queue.qsize()\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    status = await super().status()\n    status.update(queue_size=self.queue_size())\n    return status\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumer.send","title":"send","text":"
    send(message)\n

    Send a message into the worker

    Source code in fluid/utils/worker.py
    def send(self, message: MessageType | None) -> None:\n    \"\"\"Send a message into the worker\"\"\"\n    self._queue.put_nowait(message)\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker","title":"fluid.utils.worker.QueueConsumerWorker","text":"
    QueueConsumerWorker(on_message, name='')\n

    Bases: QueueConsumer[MessageType]

    A Worker that can receive and consume messages

    Source code in fluid/utils/worker.py
    def __init__(\n    self,\n    on_message: Callable[[MessageType], Awaitable[None]],\n    name: str = \"\",\n) -> None:\n    super().__init__(name=name)\n    self.on_message = on_message\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.on_message","title":"on_message instance-attribute","text":"
    on_message = on_message\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.send","title":"send","text":"
    send(message)\n

    Send a message into the worker

    Source code in fluid/utils/worker.py
    def send(self, message: MessageType | None) -> None:\n    \"\"\"Send a message into the worker\"\"\"\n    self._queue.put_nowait(message)\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    status = await super().status()\n    status.update(queue_size=self.queue_size())\n    return status\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.get_message","title":"get_message async","text":"
    get_message(timeout=0.5)\n
    Source code in fluid/utils/worker.py
    async def get_message(self, timeout: float = 0.5) -> MessageType | None:\n    try:\n        async with asyncio.timeout(timeout):\n            return await self._queue.get()\n    except asyncio.TimeoutError:\n        return None\n    except (asyncio.CancelledError, RuntimeError):\n        if not self.is_stopping():\n            raise\n    return None\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.queue_size","title":"queue_size","text":"
    queue_size()\n
    Source code in fluid/utils/worker.py
    def queue_size(self) -> int:\n    return self._queue.qsize()\n
    "},{"location":"reference/workers/#fluid.utils.worker.QueueConsumerWorker.run","title":"run async","text":"
    run()\n
    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    with self.start_running():\n        while not self.is_stopping():\n            message = await self.get_message()\n            if message is not None:\n                await self.on_message(message)\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer","title":"fluid.utils.worker.AsyncConsumer","text":"
    AsyncConsumer(dispatcher, name='')\n

    Bases: QueueConsumer[MessageType]

    A Worker that can dispatch async callbacks

    Source code in fluid/utils/worker.py
    def __init__(\n    self, dispatcher: AsyncDispatcher[MessageType], name: str = \"\"\n) -> None:\n    super().__init__(name)\n    self.dispatcher: AsyncDispatcher[MessageType] = dispatcher\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.num_workers","title":"num_workers property","text":"
    num_workers\n

    The number of workers in this worker

    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.AsyncCallback","title":"AsyncCallback instance-attribute","text":"
    AsyncCallback\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.dispatcher","title":"dispatcher instance-attribute","text":"
    dispatcher = dispatcher\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.send","title":"send","text":"
    send(message)\n

    Send a message into the worker

    Source code in fluid/utils/worker.py
    def send(self, message: MessageType | None) -> None:\n    \"\"\"Send a message into the worker\"\"\"\n    self._queue.put_nowait(message)\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    status = await super().status()\n    status.update(queue_size=self.queue_size())\n    return status\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._stopping = True\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._stopping\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.get_message","title":"get_message async","text":"
    get_message(timeout=0.5)\n
    Source code in fluid/utils/worker.py
    async def get_message(self, timeout: float = 0.5) -> MessageType | None:\n    try:\n        async with asyncio.timeout(timeout):\n            return await self._queue.get()\n    except asyncio.TimeoutError:\n        return None\n    except (asyncio.CancelledError, RuntimeError):\n        if not self.is_stopping():\n            raise\n    return None\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.queue_size","title":"queue_size","text":"
    queue_size()\n
    Source code in fluid/utils/worker.py
    def queue_size(self) -> int:\n    return self._queue.qsize()\n
    "},{"location":"reference/workers/#fluid.utils.worker.AsyncConsumer.run","title":"run async","text":"
    run()\n
    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    with self.start_running():\n        while not self.is_stopping():\n            message = await self.get_message()\n            if message is not None:\n                await self.dispatcher.dispatch(message)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers","title":"fluid.utils.worker.Workers","text":"
    Workers(\n    *workers,\n    name=\"\",\n    stopping_grace_period=STOPPING_GRACE_PERIOD\n)\n

    Bases: MultipleWorkers

    A worker managing several workers

    Source code in fluid/utils/worker.py
    def __init__(\n    self,\n    *workers: Worker,\n    name: str = \"\",\n    stopping_grace_period: int = settings.STOPPING_GRACE_PERIOD,\n) -> None:\n    super().__init__(\n        *workers, name=name, stopping_grace_period=stopping_grace_period\n    )\n    self._workers_task: asyncio.Task | None = None\n    self._delayed_callbacks: list[\n        tuple[Callable[[], None], float, float, float]\n    ] = []\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.Workers.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.running","title":"running property","text":"
    running\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return await self._workers.status()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._workers.gracefully_stop()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._workers.is_stopping()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.create_task","title":"create_task","text":"
    create_task(worker)\n
    Source code in fluid/utils/worker.py
    def create_task(self, worker: Worker) -> asyncio.Task:\n    return asyncio.create_task(\n        self._run_worker(worker), name=f\"{self.worker_name}-{worker.worker_name}\"\n    )\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n

    called after the workers are stopped

    Source code in fluid/utils/worker.py
    async def on_shutdown(self) -> None:\n    \"\"\"called after the workers are stopped\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.shutdown","title":"shutdown async","text":"
    shutdown()\n

    shutdown the workers

    Source code in fluid/utils/worker.py
    async def shutdown(self) -> None:\n    \"\"\"shutdown the workers\"\"\"\n    if self._has_shutdown:\n        return\n    self._has_shutdown = True\n    logger.warning(\n        \"gracefully stopping %d workers: %s\",\n        self.num_workers,\n        \", \".join(w.worker_name for w in self._workers.workers),\n    )\n    self.gracefully_stop()\n    try:\n        async with async_timeout.timeout(self._stopping_grace_period):\n            await self.wait_for_exit()\n        await self.on_shutdown()\n        return\n    except asyncio.TimeoutError:\n        logger.warning(\n            \"could not stop workers %s gracefully after %s\"\n            \" seconds - force shutdown\",\n            \", \".join(\n                task.get_name() for task in self._workers.tasks if not task.done()\n            ),\n            self._stopping_grace_period,\n        )\n    except asyncio.CancelledError:\n        pass\n    self._force_shutdown = True\n    self._workers.cancel()\n    try:\n        await self.wait_for_exit()\n    except asyncio.CancelledError:\n        pass\n    await self.on_shutdown()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.bail_out","title":"bail_out","text":"
    bail_out(reason, code=1)\n
    Source code in fluid/utils/worker.py
    def bail_out(self, reason: str, code: int = 1) -> None:\n    self.gracefully_stop()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.safe_run","title":"safe_run async","text":"
    safe_run()\n

    Context manager to run a worker safely

    Source code in fluid/utils/worker.py
    @asynccontextmanager\nasync def safe_run(self) -> AsyncGenerator:\n    \"\"\"Context manager to run a worker safely\"\"\"\n    try:\n        yield\n    except asyncio.CancelledError:\n        if self._force_shutdown:\n            # we are shutting down, this is expected\n            pass\n        raise\n    except Exception as e:\n        reason = f\"unhandled exception while running workers: {e}\"\n        logger.exception(reason)\n        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)\n    else:\n        # worker finished without error\n        # make sure we are shutting down\n        asyncio.get_event_loop().call_soon(self.bail_out, \"worker exit\", 1)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.add_workers","title":"add_workers","text":"
    add_workers(*workers)\n

    add workers to the workers

    Source code in fluid/utils/worker.py
    def add_workers(self, *workers: Worker) -> None:\n    \"\"\"add workers to the workers\"\"\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        if worker not in workers_:\n            workers_.append(worker)\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.run","title":"run async","text":"
    run()\n

    run the workers

    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    \"\"\"run the workers\"\"\"\n    with self.start_running():\n        async with self.safe_run():\n            workers, _ = self._workers.workers_tasks()\n            self._workers.workers = tuple(workers)\n            self._workers.tasks = tuple(\n                self.create_task(worker) for worker in workers\n            )\n            await asyncio.gather(*self._workers.tasks)\n        await self.shutdown()\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.wait_for_exit","title":"wait_for_exit async","text":"
    wait_for_exit()\n
    Source code in fluid/utils/worker.py
    async def wait_for_exit(self) -> None:\n    if self._workers_task is not None:\n        await self._workers_task\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.remove_workers","title":"remove_workers","text":"
    remove_workers(*workers)\n

    remove workers from the workers

    Source code in fluid/utils/worker.py
    def remove_workers(self, *workers: Worker) -> None:\n    \"remove workers from the workers\"\n    workers_, _ = self._workers.workers_tasks()\n    for worker in workers:\n        try:\n            workers_.remove(worker)\n        except ValueError:\n            pass\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.startup","title":"startup async","text":"
    startup()\n

    start the workers

    Source code in fluid/utils/worker.py
    async def startup(self) -> None:\n    \"\"\"start the workers\"\"\"\n    if self._workers_task is None:\n        self._workers_task = asyncio.create_task(self.run(), name=self.worker_name)\n        for args in self._delayed_callbacks:\n            self._delayed_callback(*args)\n        self._delayed_callbacks = []\n
    "},{"location":"reference/workers/#fluid.utils.worker.Workers.register_callback","title":"register_callback","text":"
    register_callback(\n    callback, seconds, jitter=0.0, periodic=False\n)\n

    Register a callback

    The callback can be periodic or not.

    Source code in fluid/utils/worker.py
    def register_callback(\n    self,\n    callback: Callable[[], None],\n    seconds: float,\n    jitter: float = 0.0,\n    periodic: bool | float = False,\n) -> None:\n    \"\"\"Register a callback\n\n    The callback can be periodic or not.\n    \"\"\"\n    if periodic is True:\n        periodic_float = seconds\n    elif periodic is False:\n        periodic_float = 0.0\n    else:\n        periodic_float = periodic\n    if not self.running:\n        self._delayed_callbacks.append((callback, seconds, jitter, periodic_float))\n    else:\n        self._delayed_callback(callback, seconds, jitter, periodic_float)\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers","title":"fluid.utils.worker.DynamicWorkers","text":"
    DynamicWorkers(\n    *workers,\n    name=\"\",\n    heartbeat=0.1,\n    stopping_grace_period=STOPPING_GRACE_PERIOD\n)\n

    Bases: MultipleWorkers

    Source code in fluid/utils/worker.py
    def __init__(\n    self,\n    *workers: Worker,\n    name: str = \"\",\n    heartbeat: float | int = 0.1,\n    stopping_grace_period: int = settings.STOPPING_GRACE_PERIOD,\n) -> None:\n    super().__init__(name)\n    self._heartbeat = heartbeat\n    self._workers = WorkerTasks()\n    self._has_shutdown = False\n    self._force_shutdown = False\n    self._stopping_grace_period = stopping_grace_period\n    self.add_workers(*workers)\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.worker_name","title":"worker_name property","text":"
    worker_name\n

    The name of the worker

    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.num_workers","title":"num_workers property","text":"
    num_workers\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.status","title":"status async","text":"
    status()\n
    Source code in fluid/utils/worker.py
    async def status(self) -> dict:\n    return await self._workers.status()\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.gracefully_stop","title":"gracefully_stop","text":"
    gracefully_stop()\n
    Source code in fluid/utils/worker.py
    def gracefully_stop(self) -> None:\n    self._workers.gracefully_stop()\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.is_running","title":"is_running","text":"
    is_running()\n
    Source code in fluid/utils/worker.py
    def is_running(self) -> bool:\n    return self._running\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.is_stopping","title":"is_stopping","text":"
    is_stopping()\n
    Source code in fluid/utils/worker.py
    def is_stopping(self) -> bool:\n    return self._workers.is_stopping()\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.start_running","title":"start_running","text":"
    start_running()\n
    Source code in fluid/utils/worker.py
    @contextmanager\ndef start_running(self) -> Generator:\n    if self._running:\n        raise RuntimeError(\"Worker is already running\")\n    self._running = True\n    try:\n        logger.info(\"%s started running\", self.worker_name)\n        yield\n    finally:\n        self._running = False\n        logger.warning(\"%s stopped running\", self.worker_name)\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.create_task","title":"create_task","text":"
    create_task(worker)\n
    Source code in fluid/utils/worker.py
    def create_task(self, worker: Worker) -> asyncio.Task:\n    return asyncio.create_task(\n        self._run_worker(worker), name=f\"{self.worker_name}-{worker.worker_name}\"\n    )\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.on_shutdown","title":"on_shutdown async","text":"
    on_shutdown()\n

    called after the workers are stopped

    Source code in fluid/utils/worker.py
    async def on_shutdown(self) -> None:\n    \"\"\"called after the workers are stopped\"\"\"\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.shutdown","title":"shutdown async","text":"
    shutdown()\n

    shutdown the workers

    Source code in fluid/utils/worker.py
    async def shutdown(self) -> None:\n    \"\"\"shutdown the workers\"\"\"\n    if self._has_shutdown:\n        return\n    self._has_shutdown = True\n    logger.warning(\n        \"gracefully stopping %d workers: %s\",\n        self.num_workers,\n        \", \".join(w.worker_name for w in self._workers.workers),\n    )\n    self.gracefully_stop()\n    try:\n        async with async_timeout.timeout(self._stopping_grace_period):\n            await self.wait_for_exit()\n        await self.on_shutdown()\n        return\n    except asyncio.TimeoutError:\n        logger.warning(\n            \"could not stop workers %s gracefully after %s\"\n            \" seconds - force shutdown\",\n            \", \".join(\n                task.get_name() for task in self._workers.tasks if not task.done()\n            ),\n            self._stopping_grace_period,\n        )\n    except asyncio.CancelledError:\n        pass\n    self._force_shutdown = True\n    self._workers.cancel()\n    try:\n        await self.wait_for_exit()\n    except asyncio.CancelledError:\n        pass\n    await self.on_shutdown()\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.bail_out","title":"bail_out","text":"
    bail_out(reason, code=1)\n
    Source code in fluid/utils/worker.py
    def bail_out(self, reason: str, code: int = 1) -> None:\n    self.gracefully_stop()\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.safe_run","title":"safe_run async","text":"
    safe_run()\n

    Context manager to run a worker safely

    Source code in fluid/utils/worker.py
    @asynccontextmanager\nasync def safe_run(self) -> AsyncGenerator:\n    \"\"\"Context manager to run a worker safely\"\"\"\n    try:\n        yield\n    except asyncio.CancelledError:\n        if self._force_shutdown:\n            # we are shutting down, this is expected\n            pass\n        raise\n    except Exception as e:\n        reason = f\"unhandled exception while running workers: {e}\"\n        logger.exception(reason)\n        asyncio.get_event_loop().call_soon(self.bail_out, reason, 2)\n    else:\n        # worker finished without error\n        # make sure we are shutting down\n        asyncio.get_event_loop().call_soon(self.bail_out, \"worker exit\", 1)\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.add_workers","title":"add_workers","text":"
    add_workers(*workers)\n

    add workers to the workers

    They can be added while the workers are running.

    Source code in fluid/utils/worker.py
    def add_workers(self, *workers: Worker) -> None:\n    \"\"\"add workers to the workers\n\n    They can be added while the workers are running.\n    \"\"\"\n    workers_, tasks_ = self._workers.workers_tasks()\n    for worker in workers:\n        workers_.append(worker)\n        tasks_.append(self.create_task(worker))\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.run","title":"run async","text":"
    run()\n
    Source code in fluid/utils/worker.py
    async def run(self) -> None:\n    with self.start_running():\n        while not self.is_stopping():\n            for worker, task in zip(self._workers.workers, self._workers.tasks):\n                if worker.is_stopping() or task.done():\n                    break\n            await asyncio.sleep(self._heartbeat)\n        await self.shutdown()\n
    "},{"location":"reference/workers/#fluid.utils.worker.DynamicWorkers.wait_for_exit","title":"wait_for_exit async","text":"
    wait_for_exit()\n
    Source code in fluid/utils/worker.py
    async def wait_for_exit(self) -> None:\n    await asyncio.gather(*self._workers.tasks)\n
    "},{"location":"tutorials/","title":"Tutorials","text":"

    The step-by-step guides, the how-to's, the recipes, and all the Aio Fluid parts you can use in your applications.

    "},{"location":"tutorials/db/","title":"Async Database","text":"

    The fluid.db module provides a simple asynchronous interface to interact with postgres databases. It is built on top of the sqlalchemy and asyncpg libraries.

    "},{"location":"tutorials/dispatchers/","title":"Event Dispatchers","text":"

    Event dispatchers are a way to decouple the event source from the event handler. This is useful when you want to have multiple handlers for the same event, or when you want to have a single handler for multiple events.

    from fluid.utils.dispatcher import SimpleDispatcher\n\nsimple = SimpleDispatcher[Any]()\n\nsimple.dispatch(\"you can dispatch anything to this generic dispatcher\")\n
    "},{"location":"tutorials/scheduler/","title":"Task Queue","text":"

    This module has a lightweight implementation of a distributed task producer (TaskScheduler) and consumer (TaskConsumer). The middleware for distributing tasks can be configured via the Broker interface. A redis broker is provided for convenience.

    "},{"location":"tutorials/scheduler/#tasks","title":"Tasks","text":"

    Tasks are standard python async functions decorated with the task decorator.

    from fluid.scheduler import task, TaskRun\n\n@task\nasync def say_hi(ctx: TaskRun):\n    return \"Hi!\"\n

    There are two types of tasks implemented

    • Simple concurrent tasks - they run concurrently with the task consumer - thy must be IO type tasks (no heavy CPU bound operations)
      from fluid.scheduler import task, TaskRun\n\n  @task\n  async def fecth_data(ctx: TaskRun):\n      # fetch data\n      data = await http_cli.get(\"https://...\")\n      data_id = await datastore_cli.stote(data)\n      # trigger another task\n      ctx.task_manager.queue(\"heavy_calculation\", data_id=data_id)\n
    • CPU bound tasks - they run on a subprocess
    from fluid.scheduler import task, TaskRun\n\n@task(cpu_bound=True)\nasync def heavy_calculation(ctx: TaskRun):\n    data = await datastore_cli.get(ctx.params[\"data_id\"])\n    # perform some heavy calculation\n    ...\n    # trigger another task\n    ctx.task_manager.queue(\"fetch_data\")\n

    Both tasks can be periodically scheduled via the schedule keyword argument:

    from datetime import timedelta\nfrom fluid.scheduler import task, TaskContext, every\n\n@task(schedule=every(timedelta(seconds=1)))\nasync def scheduled(context: TaskContext) -> str:\n    await asyncio.sleep(0.1)\n    return \"OK\"\n
    "},{"location":"tutorials/scheduler/#broker","title":"Broker","text":"

    A Task broker needs to implement three abstract methods

      @abstractmethod\n  async def queue_task(self, queued_task: QueuedTask) -> TaskRun:\n      \"\"\"Queue a task\"\"\"\n\n  @abstractmethod\n  async def get_task_run(self) -> Optional[TaskRun]:\n      \"\"\"Get a Task run from the task queue\"\"\"\n\n  @abstractmethod\n  async def queue_length(self) -> Dict[str, int]:\n      \"\"\"Length of task queues\"\"\"\n

    The library ships a Redis broker for convenience.

    from fluid.scheduler import Broker\n\nredis_broker = Broker.from_url(\"redis://localhost:6349\")\n
    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 11932d4..e5fff02 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,42 +2,50 @@ https://aio-fluid.com/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/reference/ - 2024-10-19 + 2024-10-20 + + + https://aio-fluid.com/reference/dispatchers/ + 2024-10-20 https://aio-fluid.com/reference/task_broker/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/reference/task_manager/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/reference/task_run/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/reference/tast_consumer/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/reference/workers/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/tutorials/ - 2024-10-19 + 2024-10-20 https://aio-fluid.com/tutorials/db/ - 2024-10-19 + 2024-10-20 + + + https://aio-fluid.com/tutorials/dispatchers/ + 2024-10-20 https://aio-fluid.com/tutorials/scheduler/ - 2024-10-19 + 2024-10-20 \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 64cdfed..b693a9a 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ diff --git a/tutorials/db/index.html b/tutorials/db/index.html index 8f83b8d..a7f64af 100644 --- a/tutorials/db/index.html +++ b/tutorials/db/index.html @@ -14,7 +14,7 @@ - + @@ -330,6 +330,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -531,6 +552,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -637,7 +679,7 @@

    Async Database{"base": "../..", "features": [], "search": "../../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}} + diff --git a/tutorials/dispatchers/index.html b/tutorials/dispatchers/index.html new file mode 100644 index 0000000..1a87610 --- /dev/null +++ b/tutorials/dispatchers/index.html @@ -0,0 +1,695 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Event Dispatchers - Aio Fluid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + +
    + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Event Dispatchers

    +

    Event dispatchers are a way to decouple the event source from the event handler. This is useful when you want to have multiple handlers for the same event, or when you want to have a single handler for multiple events.

    +
    from fluid.utils.dispatcher import SimpleDispatcher
    +
    +simple = SimpleDispatcher[Any]()
    +
    +simple.dispatch("you can dispatch anything to this generic dispatcher")
    +
    + + + + + + + + + + + + + +
    +
    + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/tutorials/index.html b/tutorials/index.html index 32df40d..1ed315d 100644 --- a/tutorials/index.html +++ b/tutorials/index.html @@ -330,6 +330,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -531,6 +552,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -637,7 +679,7 @@

    Tutorials{"base": "..", "features": [], "search": "../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}} + diff --git a/tutorials/scheduler/index.html b/tutorials/scheduler/index.html index e681b79..789a6e3 100644 --- a/tutorials/scheduler/index.html +++ b/tutorials/scheduler/index.html @@ -11,7 +11,7 @@ - + @@ -328,6 +328,27 @@ +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + + +
  • @@ -517,6 +538,27 @@ + + +
  • + + + + + Event Dispatchers + + + + +
  • + + + + + + + + @@ -667,72 +709,67 @@

    Task QueueTasks

    Tasks are standard python async functions decorated with the task decorator.

    -
    from fluid.scheduler import task, TaskRun
    -
    -@task
    -async def say_hi(ctx: TaskRun):
    -    return "Hi!"
    -
    - +
    from fluid.scheduler import task, TaskRun
    +
    +@task
    +async def say_hi(ctx: TaskRun):
    +    return "Hi!"
    +

    There are two types of tasks implemented

    • Simple concurrent tasks - they run concurrently with the task consumer - thy must be IO type tasks (no heavy CPU bound operations)
    -
      from fluid.scheduler import task, TaskRun
    -
    -  @task
    -  async def fecth_data(ctx: TaskRun):
    -      # fetch data
    -      data = await http_cli.get("https://...")
    -      data_id = await datastore_cli.stote(data)
    -      # trigger another task
    -      ctx.task_manager.queue("heavy_calculation", data_id=data_id)
    -
    - +
      from fluid.scheduler import task, TaskRun
    +
    +  @task
    +  async def fecth_data(ctx: TaskRun):
    +      # fetch data
    +      data = await http_cli.get("https://...")
    +      data_id = await datastore_cli.stote(data)
    +      # trigger another task
    +      ctx.task_manager.queue("heavy_calculation", data_id=data_id)
    +
    • CPU bound tasks - they run on a subprocess
    -
    from fluid.scheduler import task, TaskRun
    -
    -@task(cpu_bound=True)
    -async def heavy_calculation(ctx: TaskRun):
    -    data = await datastore_cli.get(ctx.params["data_id"])
    -    # perform some heavy calculation
    -    ...
    -    # trigger another task
    -    ctx.task_manager.queue("fetch_data")
    -
    - +
    from fluid.scheduler import task, TaskRun
    +
    +@task(cpu_bound=True)
    +async def heavy_calculation(ctx: TaskRun):
    +    data = await datastore_cli.get(ctx.params["data_id"])
    +    # perform some heavy calculation
    +    ...
    +    # trigger another task
    +    ctx.task_manager.queue("fetch_data")
    +

    Both tasks can be periodically scheduled via the schedule keyword argument:

    -
    from datetime import timedelta
    -from fluid.scheduler import task, TaskContext, every
    -
    -@task(schedule=every(timedelta(seconds=1)))
    -async def scheduled(context: TaskContext) -> str:
    -    await asyncio.sleep(0.1)
    -    return "OK"
    -
    - +
    from datetime import timedelta
    +from fluid.scheduler import task, TaskContext, every
    +
    +@task(schedule=every(timedelta(seconds=1)))
    +async def scheduled(context: TaskContext) -> str:
    +    await asyncio.sleep(0.1)
    +    return "OK"
    +

    Broker

    -

    A Task broker needs to implement three abstract methods

    -
      @abstractmethod
    -  async def queue_task(self, queued_task: QueuedTask) -> TaskRun:
    -      """Queue a task"""
    -
    -  @abstractmethod
    -  async def get_task_run(self) -> Optional[TaskRun]:
    -      """Get a Task run from the task queue"""
    -
    -  @abstractmethod
    -  async def queue_length(self) -> Dict[str, int]:
    -      """Length of task queues"""
    -
    - +

    A Task broker needs to implement three abstract methods +

      @abstractmethod
    +  async def queue_task(self, queued_task: QueuedTask) -> TaskRun:
    +      """Queue a task"""
    +
    +  @abstractmethod
    +  async def get_task_run(self) -> Optional[TaskRun]:
    +      """Get a Task run from the task queue"""
    +
    +  @abstractmethod
    +  async def queue_length(self) -> Dict[str, int]:
    +      """Length of task queues"""
    +

    The library ships a Redis broker for convenience.

    -
    from fluid.scheduler import Broker
    -
    -redis_broker = Broker.from_url("redis://localhost:6349")
    -
    +
    from fluid.scheduler import Broker
    +
    +redis_broker = Broker.from_url("redis://localhost:6349")
    +
    @@ -779,7 +816,7 @@

    Broker{"base": "../..", "features": [], "search": "../../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}} +