diff --git a/.gitignore b/.gitignore index b3d7bf3..f943d86 100644 --- a/.gitignore +++ b/.gitignore @@ -175,7 +175,7 @@ cython_debug/ # Dotflow -.output +*.output .storage .dotflow-io *.json diff --git a/README.md b/README.md index b5ffb77..c4d0a99 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,8 @@ def my_task_y(): workflow = DotFlow() -workflow.task.add(step=my_task_x, callback=my_callback) -workflow.task.add(step=my_task_y, callback=my_callback) +workflow.add(step=my_task_x, callback=my_callback) +workflow.add(step=my_task_y, callback=my_callback) workflow.start() ``` @@ -153,20 +153,20 @@ Now, simply add the `my_task_x` and `my_callback` functions you created earlier - Adding one step at a time: ```python -workflow.task.add(step=my_task_x, callback=my_callback) -workflow.task.add(step=my_task_y, callback=my_callback) +workflow.add(step=my_task_x, callback=my_callback) +workflow.add(step=my_task_y, callback=my_callback) ``` - Adding multiple steps at the same time: ```python -workflow.task.add(step=[my_task_x, my_task_y], callback=my_callback) +workflow.add(step=[my_task_x, my_task_y], callback=my_callback) ``` - Adding a step with the module path: ```python -workflow.task.add(step="module.task.my_task_x", callback=my_callback) +workflow.add(step="module.task.my_task_x", callback=my_callback) ``` #### 6. Start @@ -216,8 +216,8 @@ dotflow start --step examples.cli_with_mode.simple_step --mode parallel #### Sequential ```python -workflow.task.add(step=task_foo) -workflow.task.add(step=task_bar) +workflow.add(step=task_foo) +workflow.add(step=task_bar) workflow.start() ``` @@ -237,8 +237,8 @@ D[Finish] #### Background ```python -workflow.task.add(step=task_foo) -workflow.task.add(step=task_bar) +workflow.add(step=task_foo) +workflow.add(step=task_bar) workflow.start(mode="background") ``` @@ -259,10 +259,10 @@ D[Finish] #### Parallel ```python -workflow.task.add(step=task_a) -workflow.task.add(step=task_b) -workflow.task.add(step=task_c) -workflow.task.add(step=task_d) +workflow.add(step=task_a) +workflow.add(step=task_b) +workflow.add(step=task_c) +workflow.add(step=task_d) workflow.start(mode="parallel") ``` diff --git a/docs/nav/advanced/notify-with-telegram.md b/docs/nav/advanced/notify-with-telegram.md index d26f071..146ca3c 100644 --- a/docs/nav/advanced/notify-with-telegram.md +++ b/docs/nav/advanced/notify-with-telegram.md @@ -47,7 +47,7 @@ import os from dotflow import Config, DotFlow, action from dotflow.notify import NotifyTelegram -from dotflow.types import TypeStatus +from dotflow.types import StatusTaskType ``` ### Task function @@ -69,7 +69,7 @@ Instantiate the `NotifyTelegram` class with your Telegram bot credentials. You c notify = NotifyTelegram( token=os.getenv("TOKEN"), chat_id=os.getenv("CHAT_ID"), - notification_type=TypeStatus.FAILED # Notify only on failure + notification_type=StatusTaskType.FAILED # Notify only on failure ) ``` @@ -88,7 +88,7 @@ workflow = DotFlow( Add your defined task as a step in the workflow: ```python -workflow.task.add(step=simple_task) +workflow.add(step=simple_task) ``` ### Start diff --git a/docs/nav/reference/abc-log.md b/docs/nav/reference/abc-log.md deleted file mode 100644 index 6a669c6..0000000 --- a/docs/nav/reference/abc-log.md +++ /dev/null @@ -1,3 +0,0 @@ -# Log - -::: dotflow.abc.log.Log diff --git a/docs/nav/reference/abc-logs.md b/docs/nav/reference/abc-logs.md new file mode 100644 index 0000000..07f08b2 --- /dev/null +++ b/docs/nav/reference/abc-logs.md @@ -0,0 +1,3 @@ +# Logs + +::: dotflow.abc.logs.Logs diff --git a/docs/nav/reference/log-default.md b/docs/nav/reference/log-default.md deleted file mode 100644 index e5de777..0000000 --- a/docs/nav/reference/log-default.md +++ /dev/null @@ -1,3 +0,0 @@ -# LogDefault - -::: dotflow.providers.log_default.LogDefault diff --git a/docs/nav/reference/logs-handler.md b/docs/nav/reference/logs-handler.md new file mode 100644 index 0000000..123a0aa --- /dev/null +++ b/docs/nav/reference/logs-handler.md @@ -0,0 +1,3 @@ +# LogsHandler + +::: dotflow.plugins.logs.LogsHandler diff --git a/docs/nav/reference/notify-default.md b/docs/nav/reference/notify-default.md index 4958819..f2a6a83 100644 --- a/docs/nav/reference/notify-default.md +++ b/docs/nav/reference/notify-default.md @@ -1,3 +1,3 @@ -# NotifyTelegram +# Notify Default ::: dotflow.providers.notify_default.NotifyDefault diff --git a/docs/nav/reference/type-execution.md b/docs/nav/reference/type-execution.md index 75889ec..0e40a14 100644 --- a/docs/nav/reference/type-execution.md +++ b/docs/nav/reference/type-execution.md @@ -1,6 +1,6 @@ -# TypeExecution +# ExecutionModeType -::: dotflow.core.types.execution.TypeExecution +::: dotflow.core.types.execution.ExecutionModeType options: members: - SEQUENTIAL diff --git a/docs/nav/reference/type-status.md b/docs/nav/reference/type-status.md index a37f95b..5cce16d 100644 --- a/docs/nav/reference/type-status.md +++ b/docs/nav/reference/type-status.md @@ -1,6 +1,6 @@ -# TypeStatus +# StatusTaskType -::: dotflow.core.types.status.TypeStatus +::: dotflow.core.types.status.StatusTaskType options: members: - NOT_STARTED diff --git a/docs_src/backoff/backoff.py b/docs_src/backoff/backoff.py index acb8f5e..9f2c4ef 100644 --- a/docs_src/backoff/backoff.py +++ b/docs_src/backoff/backoff.py @@ -25,15 +25,15 @@ def run(self): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepX) + workflow.add(step=SimpleStepX) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepY) + workflow.add(step=SimpleStepY) workflow.start() workflow.task.clear() diff --git a/docs_src/callback/task_callback.py b/docs_src/callback/task_callback.py index 44c735e..e0bde23 100644 --- a/docs_src/callback/task_callback.py +++ b/docs_src/callback/task_callback.py @@ -1,14 +1,14 @@ from dotflow import DotFlow, action, Task -from dotflow.core.types.status import TypeStatus +from dotflow.core.types.status import StatusTaskType def callback_one(task: Task): - assert task.status is TypeStatus.COMPLETED + assert task.status is StatusTaskType.COMPLETED print(task.task_id, task.status, task.current_context.storage) def callback_two(task: Task): - assert task.status is TypeStatus.FAILED + assert task.status is StatusTaskType.FAILED print(task.task_id, task.status, task.current_context.storage) @@ -25,8 +25,8 @@ def simple_two(): def main(): workflow = DotFlow() - workflow.task.add(step=simple_one, callback=callback_one) - workflow.task.add(step=simple_two, callback=callback_two) + workflow.add(step=simple_one, callback=callback_one) + workflow.add(step=simple_two, callback=callback_two) workflow.start(keep_going=True) return workflow diff --git a/docs_src/callback/workflow_callback_failure.py b/docs_src/callback/workflow_callback_failure.py index 7c892c9..1d30404 100644 --- a/docs_src/callback/workflow_callback_failure.py +++ b/docs_src/callback/workflow_callback_failure.py @@ -1,7 +1,7 @@ from typing import List from dotflow import DotFlow, action, Task -from dotflow.core.types.status import TypeStatus +from dotflow.core.types.status import StatusTaskType def callback(tasks: List[Task]): @@ -9,7 +9,7 @@ def callback(tasks: List[Task]): assert len(tasks) for task in tasks: - assert task.status is TypeStatus.FAILED + assert task.status is StatusTaskType.FAILED print(task.task_id, task.status, task.current_context.storage) @@ -21,7 +21,7 @@ def simple_step(): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start(on_failure=callback) return workflow diff --git a/docs_src/callback/workflow_callback_success.py b/docs_src/callback/workflow_callback_success.py index e4bde25..b713215 100644 --- a/docs_src/callback/workflow_callback_success.py +++ b/docs_src/callback/workflow_callback_success.py @@ -1,7 +1,7 @@ from typing import List from dotflow import DotFlow, action, Task -from dotflow.core.types.status import TypeStatus +from dotflow.core.types.status import StatusTaskType def callback(tasks: List[Task]): @@ -9,7 +9,7 @@ def callback(tasks: List[Task]): assert len(tasks) for task in tasks: - assert task.status is TypeStatus.COMPLETED + assert task.status is StatusTaskType.COMPLETED print(task.task_id, task.status, task.current_context.storage) @@ -21,7 +21,7 @@ def simple_step(): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start(on_success=callback) return workflow diff --git a/docs_src/config/config.py b/docs_src/config/config.py new file mode 100644 index 0000000..2d39cf4 --- /dev/null +++ b/docs_src/config/config.py @@ -0,0 +1,20 @@ +from dotflow import Config, DotFlow, action + + +@action +def my_task(): + print("task") + + +def main(): + my_config = Config() + + workflow = DotFlow(config=my_config) + workflow.add(step=my_task) + workflow.start() + + return workflow + + +if __name__ == "__main__": + main() diff --git a/docs_src/first_steps/first_steps.py b/docs_src/first_steps/first_steps.py index 82fe724..1f371ef 100644 --- a/docs_src/first_steps/first_steps.py +++ b/docs_src/first_steps/first_steps.py @@ -1,8 +1,8 @@ -from dotflow import DotFlow, action +from dotflow import DotFlow, action, Task -def my_callback(*args, **kwargs): - print(args, kwargs) +def my_callback(task: Task): + print(task.status) @action @@ -11,5 +11,5 @@ def my_task(): workflow = DotFlow() -workflow.task.add(step=my_task, callback=my_callback) +workflow.add(step=my_task, callback=my_callback) workflow.start() diff --git a/docs_src/initial_context/initial_context.py b/docs_src/initial_context/initial_context.py index 5344dd7..b8fbea2 100644 --- a/docs_src/initial_context/initial_context.py +++ b/docs_src/initial_context/initial_context.py @@ -1,8 +1,8 @@ -from dotflow import DotFlow, action +from dotflow import DotFlow, action, Context @action -def extract_task(initial_context): +def extract_task(initial_context: Context): print(initial_context.storage, "extract") assert initial_context.storage == {"foo": True} @@ -10,7 +10,7 @@ def extract_task(initial_context): @action -def transform_task(initial_context): +def transform_task(initial_context: Context): print(initial_context.storage, "transform") assert initial_context.storage == {"bar": True} @@ -25,9 +25,9 @@ def load_task(): def main(): workflow = DotFlow() - workflow.task.add(step=extract_task, initial_context={"foo": True}) - workflow.task.add(step=transform_task, initial_context={"bar": True}) - workflow.task.add(step=load_task) + workflow.add(step=extract_task, initial_context={"foo": True}) + workflow.add(step=transform_task, initial_context={"bar": True}) + workflow.add(step=load_task) workflow.start() diff --git a/docs_src/output/step_class_result_context.py b/docs_src/output/step_class_result_context.py index 449e417..36910db 100644 --- a/docs_src/output/step_class_result_context.py +++ b/docs_src/output/step_class_result_context.py @@ -21,7 +21,7 @@ def second_function(self): def main(): workflow = DotFlow() - workflow.task.add(step=Step) + workflow.add(step=Step) workflow.start() for contexts in workflow.result_context(): diff --git a/docs_src/output/step_class_result_storage.py b/docs_src/output/step_class_result_storage.py index 45880a3..d89ab50 100644 --- a/docs_src/output/step_class_result_storage.py +++ b/docs_src/output/step_class_result_storage.py @@ -21,7 +21,7 @@ def second_function(self): def main(): workflow = DotFlow() - workflow.task.add(step=Step) + workflow.add(step=Step) workflow.start() for storages in workflow.result_storage(): diff --git a/docs_src/output/step_class_result_task.py b/docs_src/output/step_class_result_task.py index f1d9423..68b4ed1 100644 --- a/docs_src/output/step_class_result_task.py +++ b/docs_src/output/step_class_result_task.py @@ -21,7 +21,7 @@ def second_function(self): def main(): workflow = DotFlow() - workflow.task.add(step=Step) + workflow.add(step=Step) workflow.start() for task in workflow.result_task(): diff --git a/docs_src/output/step_function_result_context.py b/docs_src/output/step_function_result_context.py index 93e634b..c5d3f3b 100644 --- a/docs_src/output/step_function_result_context.py +++ b/docs_src/output/step_function_result_context.py @@ -9,7 +9,7 @@ def simple_step(): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() for context in workflow.result_context(): diff --git a/docs_src/output/step_function_result_storage.py b/docs_src/output/step_function_result_storage.py index b79ea40..08352d7 100644 --- a/docs_src/output/step_function_result_storage.py +++ b/docs_src/output/step_function_result_storage.py @@ -9,7 +9,7 @@ def simple_step(): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() for storage in workflow.result_storage(): diff --git a/docs_src/output/step_function_result_task.py b/docs_src/output/step_function_result_task.py index 9799ea5..24d76db 100644 --- a/docs_src/output/step_function_result_task.py +++ b/docs_src/output/step_function_result_task.py @@ -9,7 +9,7 @@ def simple_step(): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() for task in workflow.result_task(): diff --git a/docs_src/previous_context/previous_context.py b/docs_src/previous_context/previous_context.py index be2d9d6..4c07681 100644 --- a/docs_src/previous_context/previous_context.py +++ b/docs_src/previous_context/previous_context.py @@ -26,9 +26,9 @@ def load_task(previous_context: Context): def main(): workflow = DotFlow() - workflow.task.add(step=extract_task) - workflow.task.add(step=transform_task) - workflow.task.add(step=load_task) + workflow.add(step=extract_task) + workflow.add(step=transform_task) + workflow.add(step=load_task) workflow.start() diff --git a/docs_src/process_mode/background.py b/docs_src/process_mode/background.py index 723991d..afe4d35 100644 --- a/docs_src/process_mode/background.py +++ b/docs_src/process_mode/background.py @@ -20,8 +20,8 @@ def task_bar(initial_context: Context): def main(): workflow = DotFlow() - workflow.task.add(step=task_foo, initial_context=10) - workflow.task.add(step=task_bar, initial_context=10) + workflow.add(step=task_foo, initial_context=10) + workflow.add(step=task_bar, initial_context=10) workflow.start(mode="background") diff --git a/docs_src/process_mode/parallel.py b/docs_src/process_mode/parallel.py index 15d8714..e62b539 100644 --- a/docs_src/process_mode/parallel.py +++ b/docs_src/process_mode/parallel.py @@ -20,8 +20,8 @@ def task_bar(initial_context: Context): def main(): workflow = DotFlow() - workflow.task.add(step=task_foo, initial_context=10) - workflow.task.add(step=task_bar, initial_context=10) + workflow.add(step=task_foo, initial_context=10) + workflow.add(step=task_bar, initial_context=10) workflow.start(mode="parallel") diff --git a/docs_src/process_mode/parallel_group.py b/docs_src/process_mode/parallel_group.py index 9108be5..b307740 100644 --- a/docs_src/process_mode/parallel_group.py +++ b/docs_src/process_mode/parallel_group.py @@ -20,8 +20,8 @@ def task_bar(initial_context: Context): def main(): workflow = DotFlow() - workflow.task.add(step=task_foo, initial_context=10, group_name="foo") - workflow.task.add(step=task_bar, initial_context=10, group_name="bar") + workflow.add(step=task_foo, initial_context=10, group_name="foo") + workflow.add(step=task_bar, initial_context=10, group_name="bar") workflow.start() diff --git a/docs_src/process_mode/sequential.py b/docs_src/process_mode/sequential.py index e33a1f9..5272a94 100644 --- a/docs_src/process_mode/sequential.py +++ b/docs_src/process_mode/sequential.py @@ -20,8 +20,8 @@ def task_bar(initial_context: Context): def main(): workflow = DotFlow() - workflow.task.add(step=task_foo, initial_context=10) - workflow.task.add(step=task_bar, initial_context=10) + workflow.add(step=task_foo, initial_context=10) + workflow.add(step=task_bar, initial_context=10) workflow.start(mode="sequential") diff --git a/docs_src/retry/retry.py b/docs_src/retry/retry.py index 7089197..7d2eeee 100644 --- a/docs_src/retry/retry.py +++ b/docs_src/retry/retry.py @@ -25,15 +25,15 @@ def run(self): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepX) + workflow.add(step=SimpleStepX) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepY) + workflow.add(step=SimpleStepY) workflow.start() workflow.task.clear() diff --git a/docs_src/retry/retry_delay.py b/docs_src/retry/retry_delay.py index 3c72208..2c6e380 100644 --- a/docs_src/retry/retry_delay.py +++ b/docs_src/retry/retry_delay.py @@ -25,15 +25,15 @@ def run(self): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepX) + workflow.add(step=SimpleStepX) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepY) + workflow.add(step=SimpleStepY) workflow.start() workflow.task.clear() diff --git a/docs_src/timeout/timeout.py b/docs_src/timeout/timeout.py index 910586e..dc3a886 100644 --- a/docs_src/timeout/timeout.py +++ b/docs_src/timeout/timeout.py @@ -26,15 +26,15 @@ def __init__(self): def main(): workflow = DotFlow() - workflow.task.add(step=simple_step) + workflow.add(step=simple_step) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepX) + workflow.add(step=SimpleStepX) workflow.start() workflow.task.clear() - workflow.task.add(step=SimpleStepY) + workflow.add(step=SimpleStepY) workflow.start() workflow.task.clear() diff --git a/dotflow/abc/flow.py b/dotflow/abc/flow.py index 3840dd1..4f86d9e 100644 --- a/dotflow/abc/flow.py +++ b/dotflow/abc/flow.py @@ -11,16 +11,14 @@ class Flow(ABC): def __init__( self, - tasks: List[Task], workflow_id: UUID, ignore: bool, - groups: Dict[str, List[Task]] + group: Dict[str, List[Task]] ) -> None: self.queue = None - self.tasks = tasks self.workflow_id = workflow_id self.ignore = ignore - self.groups = groups + self.group = group self.setup_queue() self.run() @@ -30,7 +28,7 @@ def setup_queue(self) -> None: self.queue = [] @abstractmethod - def get_tasks(self) -> List[Task]: + def transport(self) -> List[Task]: return self.queue @abstractmethod diff --git a/dotflow/abc/log.py b/dotflow/abc/log.py deleted file mode 100644 index 5f22904..0000000 --- a/dotflow/abc/log.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Notify ABC""" - -from typing import Any - -from abc import ABC, abstractmethod - - -class Log(ABC): - """Log""" - - @abstractmethod - def info(self, task: Any) -> None: - """Info""" - - @abstractmethod - def error(self, task: Any) -> None: - """Error""" diff --git a/dotflow/abc/logs.py b/dotflow/abc/logs.py new file mode 100644 index 0000000..379d526 --- /dev/null +++ b/dotflow/abc/logs.py @@ -0,0 +1,38 @@ +"""Logs ABC""" + +from typing import Literal + +from abc import ABC, abstractmethod + +from dotflow.core.types.enum import StrEnum + + +class TypeLog(StrEnum): + + CRITICAL = "CRITICAL" + ERROR = "ERROR" + WARNING = "WARNING" + INFO = "INFO" + DEBUG = "DEBUG" + NOTSET = "NOTSET" + + +TYPE_LOG = Literal[ + TypeLog.CRITICAL, + TypeLog.ERROR, + TypeLog.WARNING, + TypeLog.INFO, + TypeLog.DEBUG, + TypeLog.NOTSET, +] + +TASK_LOG_FORMAT = "{task_id}: {workflow_id} - {status}" +WORKFLOW_LOG_FORMAT = "{workflow_id}: {status}" + + +class Logs(ABC): + """Logs""" + + @abstractmethod + def on_task_status_change(self, task_object, type: TYPE_LOG) -> None: + pass diff --git a/dotflow/cli/commands/log.py b/dotflow/cli/commands/log.py index 619f98d..e7d78aa 100644 --- a/dotflow/cli/commands/log.py +++ b/dotflow/cli/commands/log.py @@ -18,4 +18,4 @@ def setup(self): f"To access all logs, open the file ({settings.LOG_PATH.resolve()})." ) else: - print(settings.WARNING_ALERT, "Log file not found.") + print(settings.WARNING_ALERT, "Logs file not found.") diff --git a/dotflow/cli/commands/start.py b/dotflow/cli/commands/start.py index 64aa8b9..9119914 100644 --- a/dotflow/cli/commands/start.py +++ b/dotflow/cli/commands/start.py @@ -4,7 +4,7 @@ from dotflow import DotFlow, Config from dotflow.providers import StorageDefault, StorageFile -from dotflow.core.types.execution import TypeExecution +from dotflow.core.types.execution import ExecutionModeType from dotflow.cli.command import Command @@ -13,7 +13,7 @@ class StartCommand(Command): def setup(self): workflow = self._new_workflow() - workflow.task.add( + workflow.add( step=self.params.step, callback=self.params.callback, initial_context=self.params.initial_context, @@ -21,7 +21,7 @@ def setup(self): workflow.start(mode=self.params.mode) - if self.params.mode == TypeExecution.BACKGROUND: + if self.params.mode == ExecutionModeType.BACKGROUND: system("/bin/bash") def _new_workflow(self): diff --git a/dotflow/cli/setup.py b/dotflow/cli/setup.py index 8e90f33..3e4b523 100644 --- a/dotflow/cli/setup.py +++ b/dotflow/cli/setup.py @@ -6,7 +6,7 @@ from dotflow.logging import logger from dotflow.settings import Settings as settings from dotflow.utils.basic_functions import basic_callback -from dotflow.core.types import TypeExecution, TypeStorage +from dotflow.core.types import ExecutionModeType, StorageType from dotflow.core.exception import ( MissingActionDecorator, ExecutionModeNotExist, @@ -53,17 +53,17 @@ def setup_start(self): self.cmd_start.add_argument("-c", "--callback", default=basic_callback) self.cmd_start.add_argument("-i", "--initial-context") self.cmd_start.add_argument( - "-o", "--storage", choices=[TypeStorage.DEFAULT, TypeStorage.FILE] + "-o", "--storage", choices=[StorageType.DEFAULT, StorageType.FILE] ) self.cmd_start.add_argument("-p", "--path", default=settings.START_PATH) self.cmd_start.add_argument( "-m", "--mode", - default=TypeExecution.SEQUENTIAL, + default=ExecutionModeType.SEQUENTIAL, choices=[ - TypeExecution.SEQUENTIAL, - TypeExecution.BACKGROUND, - TypeExecution.PARALLEL, + ExecutionModeType.SEQUENTIAL, + ExecutionModeType.BACKGROUND, + ExecutionModeType.PARALLEL, ], ) diff --git a/dotflow/core/config.py b/dotflow/core/config.py index 480bfe2..daa728a 100644 --- a/dotflow/core/config.py +++ b/dotflow/core/config.py @@ -1,17 +1,26 @@ """Config module""" -from typing import Optional +from typing import List, Optional -from dotflow.abc.log import Log from dotflow.abc.storage import Storage from dotflow.abc.notify import Notify +from dotflow.abc.logs import Logs -from dotflow.providers.log_default import LogDefault +from dotflow.plugins.logs import LogsHandler from dotflow.providers.storage_default import StorageDefault from dotflow.providers.notify_default import NotifyDefault +from dotflow.utils.tools import start_and_validate_instance -class Config: +class ConfigInstance: + + def __init__(self, *_args, **_kwargs): + self._storage: Storage = None + self._notify: Notify = None + self._logs: List[Logs] = None + + +class Config(ConfigInstance): """ Import: You can import the **Config** class with: @@ -21,7 +30,7 @@ class Config: from dotflow.providers import ( StorageDefault, NotifyDefault, - LogDefault + LogsHandler ) Example: @@ -30,26 +39,67 @@ class Config: config = Config( storage=StorageFile(path=".output"), notify=NotifyDefault(), - log=LogDefault() + logs=LogsHandler ) Args: - storage (Optional[Storage]): Type of the storage. - notify (Optional[Notify]): Type of the notify. - log (Optional[Log]): Type of the notify. + storage (Optional[Storage]): Type of the Storage. + notify (Optional[Notify]): Type of the Notify. + logs (Optional[List[Logs]]): Type of the Logs. Attributes: storage (Optional[Storage]): notify (Optional[Notify]): - log (Optional[Log]): + logs (Optional[List[Logs]]): """ def __init__( self, - storage: Optional[Storage] = StorageDefault(), - notify: Optional[Notify] = NotifyDefault(), - log: Optional[Log] = LogDefault(), - ) -> None: + storage: Optional[Storage] = StorageDefault, + notify: Optional[Notify] = NotifyDefault, + logs: Optional[List[Logs]] = LogsHandler + ): + super().__init__() self.storage = storage self.notify = notify - self.log = log + self.logs = logs + + @property + def storage(self): + return self._storage + + @storage.setter + def storage(self, value): + if not value: + value = StorageDefault() + + self._storage = start_and_validate_instance(value, Storage) + + @property + def notify(self): + return self._notify + + @notify.setter + def notify(self, value): + if not value: + value = NotifyDefault() + + self._notify = start_and_validate_instance(value, Notify) + + @property + def logs(self): + return self._logs + + @logs.setter + def logs(self, value): + if not value: + value = [LogsHandler] + + if isinstance(value, list): + if isinstance(value, list): + for index, current_log in enumerate(value): + value[index] = start_and_validate_instance(current_log, Logs) + else: + value = [start_and_validate_instance(value, Logs)] + + self._logs = value diff --git a/dotflow/core/dotflow.py b/dotflow/core/dotflow.py index c5b271a..3c8e6bb 100644 --- a/dotflow/core/dotflow.py +++ b/dotflow/core/dotflow.py @@ -1,15 +1,27 @@ -"""DotFlow""" +"""DotFlow module""" -from uuid import uuid4 +from uuid import uuid4, UUID from functools import partial -from typing import Optional + +from typing import Callable, Optional from dotflow.core.config import Config from dotflow.core.workflow import Manager from dotflow.core.task import TaskBuilder +from dotflow.utils.tools import start_and_validate_instance + + +class DotflowInstance: + def __init__(self, *_args, **_kwargs): + self._config: Config = None + self._workflow_id: UUID = None + self._task: TaskBuilder = None + self._add: Callable = None + self._start: Manager = None -class DotFlow: + +class DotFlow(DotflowInstance): """ Import: You can import the **Dotflow** class directly from dotflow: @@ -27,54 +39,104 @@ class DotFlow: workflow = DotFlow(config=config) Args: - config (Optional[Config]): Configuration class. + config (Optional[Config]): Config class. Attributes: workflow_id (UUID): - task (List[Task]): + task (TaskBuilder): + + add (Callable): start (Manager): """ - def __init__( - self, - config: Optional[Config] = None - ) -> None: - self.workflow_id = uuid4() - config = config if config else Config() - - self.task = TaskBuilder( - config=config, - workflow_id=self.workflow_id - ) - - self.start = partial( - Manager, - tasks=self.task.queue, - workflow_id=self.workflow_id - ) + def __init__(self, config: Optional[Config] = Config) -> None: + super().__init__() + self.config: Config = config + self.workflow_id: UUID = None + self.task: TaskBuilder = None + self.add: Callable = None + self.start: Manager = None + + @property + def config(self): + return self._config + + @config.setter + def config(self, value): + if not value: + value = Config() + + self._config = start_and_validate_instance(value, Config) + + @property + def workflow_id(self): + return self._workflow_id + + @workflow_id.setter + def workflow_id(self, value): + if not value: + value = uuid4() + self._workflow_id = value + + @property + def task(self): + return self._task + + @task.setter + def task(self, value): + if not value: + value = TaskBuilder( + config=self.config, + workflow_id=self.workflow_id + ) + self._task = value + + @property + def add(self): + return self._add + + @add.setter + def add(self, value): + if not value: + value = self.task.add + self._add = value + + @property + def start(self): + return self._start + + @start.setter + def start(self, value): + if not value: + value = partial( + Manager, + group=self.task.group, + workflow_id=self.workflow_id + ) + self._start = value def result_task(self): """ Returns: list (List[Task]): Returns a list of Task class. """ - return self.task.queue + return self.task.group.tasks() def result_context(self): """ Returns: list (List[Context]): Returns a list of Context class. """ - return [task.current_context for task in self.task.queue] + return [task.current_context for task in self.task.group.tasks()] def result_storage(self): """ Returns: list (List[Any]): Returns a list of assorted objects. """ - return [task.current_context.storage for task in self.task.queue] + return [task.current_context.storage for task in self.task.group.tasks()] def result(self): return self.task.result() diff --git a/dotflow/core/exception.py b/dotflow/core/exception.py index b4a5d0b..ad0e24e 100644 --- a/dotflow/core/exception.py +++ b/dotflow/core/exception.py @@ -1,7 +1,7 @@ """Exception module""" MESSAGE_UNKNOWN_ERROR = "Unknown error, please check logs for more information." -MESSAGE_MISSING_STEP_DECORATOR = "A step function necessarily needs an '@action' decorator to circulate in the workflow. For more implementation details, access the documentation: https://dotflow-io.github.io/dotflow/#3-task-function." +MESSAGE_MISSING_STEP_DECORATOR = "A step function necessarily needs an '@action' decorator to circulate in the workflow. For more implementation details, access the documentation: https://dotflow-io.github.io/dotflow/nav/tutorial/first-steps/#3-task-function." MESSAGE_NOT_CALLABLE_OBJECT = "Problem validating the '{name}' object type; this is not a callable object" MESSAGE_EXECUTION_NOT_EXIST = "The execution mode does not exist. Allowed parameter is 'sequential', 'background' and 'parallel'." MESSAGE_IMPORT_MODULE_ERROR = "Error importing Python module '{module}'." diff --git a/dotflow/core/execution.py b/dotflow/core/execution.py index 8d33149..dc75442 100644 --- a/dotflow/core/execution.py +++ b/dotflow/core/execution.py @@ -16,31 +16,31 @@ from dotflow.core.action import Action from dotflow.core.context import Context from dotflow.core.task import Task -from dotflow.core.types import TypeStatus +from dotflow.core.types import StatusTaskType from dotflow.utils import basic_callback +VALID_OBJECTS = [ + str, + int, + float, + complex, + dict, + list, + tuple, + set, + frozenset, + range, + bool, + FunctionType, + NoneType, + bytes, + bytearray, + memoryview, +] -class Execution: - VALID_OBJECTS = [ - str, - int, - float, - complex, - dict, - list, - tuple, - set, - frozenset, - range, - bool, - FunctionType, - NoneType, - bytes, - bytearray, - memoryview, - ] +class Execution: def __init__( self, @@ -50,7 +50,7 @@ def __init__( _flow_callback: Callable = basic_callback, ) -> None: self.task = task - self.task.status = TypeStatus.IN_PROGRESS + self.task.status = StatusTaskType.IN_PROGRESS self.task.previous_context = previous_context self.task.workflow_id = workflow_id @@ -146,14 +146,14 @@ def _excution(self, _flow_callback): task=self.task, ) - if type(current_context.storage) not in self.VALID_OBJECTS: + if type(current_context.storage) not in VALID_OBJECTS: current_context = self._execution_with_class( class_instance=current_context.storage ) self.task.current_context = current_context self.task.duration = (datetime.now() - start).total_seconds() - self.task.status = TypeStatus.COMPLETED + self.task.status = StatusTaskType.COMPLETED except AssertionError as err: raise err @@ -161,7 +161,7 @@ def _excution(self, _flow_callback): except Exception as err: self.task.error = err self.task.current_context = None - self.task.status = TypeStatus.FAILED + self.task.status = StatusTaskType.FAILED finally: self.task.callback(task=self.task) diff --git a/dotflow/core/task.py b/dotflow/core/task.py index aee9c31..8378b36 100644 --- a/dotflow/core/task.py +++ b/dotflow/core/task.py @@ -3,8 +3,9 @@ import json from uuid import UUID -from typing import Any, Callable, List +from typing import Any, Callable, List, Dict +from dotflow.abc.logs import TypeLog from dotflow.core.config import Config from dotflow.core.action import Action from dotflow.core.context import Context @@ -12,13 +13,15 @@ from dotflow.core.serializers.task import SerializerTask from dotflow.core.serializers.workflow import SerializerWorkflow from dotflow.core.exception import MissingActionDecorator, NotCallableObject -from dotflow.core.types.status import TypeStatus +from dotflow.core.types.status import StatusTaskType, TYPE_STATUS_TASK from dotflow.utils import ( basic_callback, traceback_error, message_error ) +TASK_GROUP_NAME = "default" + class TaskInstance: """ @@ -28,7 +31,7 @@ class TaskInstance: from dotflow.core.task import TaskInstance """ - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *_args, **_kwargs) -> None: self.task_id = None self.workflow_id = None self._step = None @@ -65,12 +68,12 @@ class Task(TaskInstance): step (Callable): A argument that receives an object of the callable type, which is basically a function. You can see in this - [example](https://dotflow-io.github.io/dotflow/#3-task-function). + [example](https://dotflow-io.github.io/dotflow/nav/tutorial/first-steps/#3-task-function). callback (Callable): Any callable object that receives **args** or **kwargs**, which is basically a function. You can see in this - [example](https://dotflow-io.github.io/dotflow/#2-callback-function). + [example](https://dotflow-io.github.io/dotflow/nav/tutorial/first-steps/#2-callback-function). initial_context (Any): Any python object. @@ -89,7 +92,7 @@ def __init__( initial_context: Any = None, workflow_id: UUID = None, config: Config = None, - group_name: str = "default" + group_name: str = TASK_GROUP_NAME ) -> None: super().__init__( task_id, @@ -107,7 +110,7 @@ def __init__( self.step = step self.callback = callback self.initial_context = initial_context - self.status = TypeStatus.NOT_STARTED + self.status = StatusTaskType.NOT_STARTED @property def step(self): @@ -203,20 +206,23 @@ def error(self, value: Exception) -> None: task_error = TaskError(value) self._error = task_error - self.config.log.error(task=self) + for log in self.config.logs: + log.on_task_status_change(task_object=self, type=TypeLog.ERROR) @property def status(self): if not self._status: - return TypeStatus.NOT_STARTED + return StatusTaskType.NOT_STARTED return self._status @status.setter - def status(self, value: TypeStatus) -> None: + def status(self, value: TYPE_STATUS_TASK) -> None: self._status = value self.config.notify.send(task=self) - self.config.log.info(task=self) + + for log in self.config.logs: + log.on_task_status_change(task_object=self, type=TypeLog.INFO) @property def config(self): @@ -271,7 +277,7 @@ def __init__( config: Config, workflow_id: UUID = None ) -> None: - self.queue: List[Callable] = [] + self.group: QueueGroup = QueueGroup() self.workflow_id = workflow_id self.config = config @@ -280,19 +286,19 @@ def add( step: Callable, callback: Callable = basic_callback, initial_context: Any = None, - group_name: str = "default" + group_name: str = TASK_GROUP_NAME ) -> None: """ Args: step (Callable): A argument that receives an object of the callable type, which is basically a function. You can see in this - [example](https://dotflow-io.github.io/dotflow/#3-task-function). + [example](https://dotflow-io.github.io/dotflow/nav/tutorial/first-steps/#3-task-function). callback (Callable): Any callable object that receives **args** or **kwargs**, which is basically a function. You can see in this - [example](https://dotflow-io.github.io/dotflow/#2-callback-function). + [example](https://dotflow-io.github.io/dotflow/nav/tutorial/first-steps/#2-callback-function). initial_context (Context): The argument exists to include initial data in the execution @@ -313,9 +319,9 @@ def add( ) return self - self.queue.append( - Task( - task_id=len(self.queue), + self.group.add( + item=Task( + task_id=self.group.size(), step=step, callback=Module(value=callback), initial_context=initial_context, @@ -339,9 +345,64 @@ def reverse(self) -> None: def schema(self) -> SerializerWorkflow: return SerializerWorkflow( workflow_id=self.workflow_id, - tasks=[item.schema() for item in self.queue] + tasks=[item.schema() for item in self.group.tasks()] ) def result(self) -> SerializerWorkflow: item = self.schema().model_dump_json() return json.loads(item) + + +class QueueGroup: + + def __init__(self): + self.queue: Dict[str, Queue] = {} + + def add(self, item: Task) -> None: + if not self.queue.get(item.group_name): + self.queue[item.group_name] = Queue() + + self.queue[item.group_name].add(item=item) + + def size(self) -> int: + current_size = 0 + for _, group in self.queue.items(): + current_size += group.size() + + return current_size + + def count(self) -> int: + return len(self.queue) + + def tasks(self) -> List[Task]: + tasks = [] + for _, queue in self.queue.items(): + tasks += queue.tasks + + return tasks + + +class Queue: + + INITIAL_INDEX = 0 + + def __init__(self): + self.tasks: List[Task] = [] + + def add(self, item: Task) -> None: + self.tasks.append(item) + + def remove(self) -> Task: + return self.tasks.pop(self.INITIAL_INDEX) + + def size(self) -> int: + return len(self.tasks) + + def reverse(self) -> None: + self.tasks.reverse() + + def clear(self) -> None: + self.tasks.clear() + + def get(self) -> List[Task]: + return self.tasks diff --git a/dotflow/core/types/__init__.py b/dotflow/core/types/__init__.py index d11f57f..d687de5 100644 --- a/dotflow/core/types/__init__.py +++ b/dotflow/core/types/__init__.py @@ -1,12 +1,14 @@ """Types __init__ module.""" -from dotflow.core.types.execution import TypeExecution -from dotflow.core.types.status import TypeStatus -from dotflow.core.types.storage import TypeStorage +from dotflow.core.types.execution import ExecutionModeType +from dotflow.core.types.status import StatusTaskType +from dotflow.core.types.storage import StorageType +from dotflow.core.types.worflow import WorkflowStatusType __all__ = [ - "TypeExecution", - "TypeStatus", - "TypeStorage" + "ExecutionModeType", + "StatusTaskType", + "StorageType", + "WorkflowStatusType" ] diff --git a/dotflow/core/types/enum.py b/dotflow/core/types/enum.py new file mode 100644 index 0000000..3d8bf4b --- /dev/null +++ b/dotflow/core/types/enum.py @@ -0,0 +1,18 @@ +"""Enum Type module""" + +import sys + +from enum import Enum + + +class StrEnumType(str, Enum): + ... + + +if sys.version_info >= (3, 11): + from enum import StrEnum as StrEnum +else: + StrEnum = StrEnumType + + +__all__ = ["StrEnum"] diff --git a/dotflow/core/types/execution.py b/dotflow/core/types/execution.py index ed6a069..321ea7c 100644 --- a/dotflow/core/types/execution.py +++ b/dotflow/core/types/execution.py @@ -1,16 +1,25 @@ -"""Type Execution mode module""" +"""Execution Mode Type module""" -from typing_extensions import Annotated, Doc +from typing import Literal +from dotflow.core.types.enum import StrEnum -class TypeExecution: + +class ExecutionModeType(StrEnum): """ Import: - You can import the **TypeExecution** class with: + You can import the **ExecutionModeType** class with: - from dotflow.core.types import TypeExecution + from dotflow.core.types import ExecutionModeType """ - SEQUENTIAL: Annotated[str, Doc("Sequential execution.")] = "sequential" - BACKGROUND: Annotated[str, Doc("Background execution.")] = "background" - PARALLEL: Annotated[str, Doc("Parallel execution.")] = "parallel" + SEQUENTIAL = "sequential" + BACKGROUND = "background" + PARALLEL = "parallel" + + +TYPE_MODE_EXECUTION = Literal[ + ExecutionModeType.SEQUENTIAL, + ExecutionModeType.BACKGROUND, + ExecutionModeType.PARALLEL +] diff --git a/dotflow/core/types/status.py b/dotflow/core/types/status.py index 83e7758..07923f2 100644 --- a/dotflow/core/types/status.py +++ b/dotflow/core/types/status.py @@ -1,31 +1,31 @@ -"""Type TypeStatus mode module""" +"""Status Task Type module""" -from typing_extensions import Annotated, Doc +from typing_extensions import Literal +from dotflow.core.types.enum import StrEnum -class TypeStatus: + +class StatusTaskType(StrEnum): """ Import: - You can import the **TypeStatus** class with: + You can import the **StatusTaskType** class with: - from dotflow.core.types import TypeStatus + from dotflow.core.types import StatusTaskType """ - NOT_STARTED: Annotated[str, Doc("Status not started.")] = "Not started" - IN_PROGRESS: Annotated[str, Doc("Status in progress.")] = "In progress" - COMPLETED: Annotated[str, Doc("Status completed.")] = "Completed" - PAUSED: Annotated[str, Doc("Status paused.")] = "Paused" - RETRY: Annotated[str, Doc("Status retry.")] = "Retry" - FAILED: Annotated[str, Doc("Status failed.")] = "Failed" - - @classmethod - def get_symbol(cls, value: str) -> str: - status = { - TypeStatus.NOT_STARTED: "⚪", - TypeStatus.IN_PROGRESS: "🔵", - TypeStatus.COMPLETED: "✅", - TypeStatus.PAUSED: "◼️", - TypeStatus.RETRY: "❗", - TypeStatus.FAILED: "❌" - } - return status.get(value) + NOT_STARTED = "Not started" + IN_PROGRESS = "In progress" + COMPLETED = "Completed" + PAUSED = "Paused" + RETRY = "Retry" + FAILED = "Failed" + + +TYPE_STATUS_TASK = Literal[ + StatusTaskType.NOT_STARTED, + StatusTaskType.IN_PROGRESS, + StatusTaskType.COMPLETED, + StatusTaskType.PAUSED, + StatusTaskType.RETRY, + StatusTaskType.FAILED +] diff --git a/dotflow/core/types/storage.py b/dotflow/core/types/storage.py index 9e8ac87..dc0971a 100644 --- a/dotflow/core/types/storage.py +++ b/dotflow/core/types/storage.py @@ -1,15 +1,23 @@ -"""Type Storage mode""" +"""Storage Type module""" -from typing_extensions import Annotated, Doc +from typing import Literal +from dotflow.core.types.enum import StrEnum -class TypeStorage: + +class StorageType(StrEnum): """ Import: - You can import the **TypeStorage** class with: + You can import the **StorageType** class with: - from dotflow.core.types import TypeStorage + from dotflow.core.types import StorageType """ - DEFAULT: Annotated[str, Doc("Default storage.")] = "default" - FILE: Annotated[str, Doc("File storage.")] = "file" + DEFAULT = "default" + FILE = "file" + + +STORAGE_TYPE = Literal[ + StorageType.DEFAULT, + StorageType.FILE +] diff --git a/dotflow/core/types/worflow.py b/dotflow/core/types/worflow.py index 4835e39..25b8940 100644 --- a/dotflow/core/types/worflow.py +++ b/dotflow/core/types/worflow.py @@ -1,14 +1,25 @@ -"""Step Workflow Status module""" +"""Workflow Status Type module""" +from typing import Literal -class WorkflowStatus: +from dotflow.core.types.enum import StrEnum + + +class WorkflowStatusType(StrEnum): """ Import: - You can import the **WorkflowStatus** class with: + You can import the **WorkflowStatusType** class with: - from dotflow.core.types import WorkflowStatus + from dotflow.core.types import WorkflowStatusType """ NEW = "New" IN_PROGRESS = "In progress" COMPLETED = "Completed" + + +WORKFLOW_STATUS_TYPE = Literal[ + WorkflowStatusType.NEW, + WorkflowStatusType.IN_PROGRESS, + WorkflowStatusType.COMPLETED +] diff --git a/dotflow/core/workflow.py b/dotflow/core/workflow.py index accf05d..cfdf051 100644 --- a/dotflow/core/workflow.py +++ b/dotflow/core/workflow.py @@ -1,38 +1,32 @@ """Workflow module""" +import sys import threading import warnings import platform +import multiprocessing as mp +from queue import Empty from datetime import datetime from multiprocessing import Process, Queue from uuid import UUID, uuid4 -from typing import Callable, Dict, List +from typing import Callable, List from dotflow.abc.flow import Flow from dotflow.core.context import Context from dotflow.core.execution import Execution from dotflow.core.exception import ExecutionModeNotExist -from dotflow.core.types import TypeExecution, TypeStatus -from dotflow.core.task import Task +from dotflow.core.types import ExecutionModeType, StatusTaskType +from dotflow.core.task import Task, QueueGroup from dotflow.utils import basic_callback - -def is_darwin() -> bool: - """Is Darwin""" - return platform.system() == "Darwin" +# mp.set_start_method("spawn", force=True) -def grouper(tasks: List[Task]) -> Dict[str, List[Task]]: - """Grouper""" - groups = {} - for task in tasks: - if not groups.get(task.group_name): - groups[task.group_name] = [] - groups[task.group_name].append(task) - - return groups +def is_darwin_arm() -> bool: + """Is Darwin""" + return platform.system() == "Darwin" and platform.processor() == "arm" class Manager: @@ -53,8 +47,8 @@ class Manager: ) Args: - tasks (List[Task]): - A list containing objects of type Task. + group (QueueGroup): + A group containing objects of type Task. on_success (Callable): Success function to be executed after the completion of the entire @@ -64,7 +58,7 @@ class Manager: Failure function to be executed after the completion of the entire workflow. It's essentially a callback for error scenarios - mode (TypeExecution): + mode (ExecutionModeType): Parameter that defines the execution mode of the workflow. Currently, there are options to execute in **sequential**, **background**, or **parallel** mode. The sequential mode is used by default. @@ -90,21 +84,19 @@ class Manager: def __init__( self, - tasks: List[Task], + group: QueueGroup, on_success: Callable = basic_callback, on_failure: Callable = basic_callback, - mode: TypeExecution = TypeExecution.SEQUENTIAL, + mode: ExecutionModeType = ExecutionModeType.SEQUENTIAL, keep_going: bool = False, workflow_id: UUID = None, ) -> None: - self.tasks = tasks + self.group = group self.on_success = on_success self.on_failure = on_failure self.workflow_id = workflow_id or uuid4() self.started = datetime.now() - execution = None - groups = grouper(tasks=tasks) try: execution = getattr(self, mode) @@ -112,47 +104,57 @@ def __init__( raise ExecutionModeNotExist() from err self.tasks = execution( - tasks=tasks, workflow_id=workflow_id, ignore=keep_going, groups=groups + workflow_id=workflow_id, + ignore=keep_going, + group=group ) - self._callback_workflow(tasks=self.tasks) + self._callback_workflow(queue_group=self.group) - def _callback_workflow(self, tasks: List[Task]): + def _callback_workflow(self, queue_group: QueueGroup): + tasks = queue_group.tasks() final_status = [task.status for task in tasks] - if TypeStatus.FAILED in final_status: + if StatusTaskType.FAILED in final_status: self.on_failure(tasks=tasks) else: self.on_success(tasks=tasks) def sequential(self, **kwargs) -> List[Task]: - if len(kwargs.get("groups", {})) > 1 and not is_darwin(): + """Sequential execution""" + many_groups = 1 + group: QueueGroup = kwargs.get("group") + + if group.size() < many_groups: process = SequentialGroup(**kwargs) - return process.get_tasks() + return process.transport() process = Sequential(**kwargs) - return process.get_tasks() + return process.transport() def sequential_group(self, **kwargs): + """Sequential Group execution""" process = SequentialGroup(**kwargs) - return process.get_tasks() + return process.transport() def background(self, **kwargs) -> List[Task]: + """Background execution""" process = Background(**kwargs) - return process.get_tasks() + return process.transport() def parallel(self, **kwargs) -> List[Task]: - if is_darwin(): + """Parallel execution""" + if is_darwin_arm(): warnings.warn( "Parallel mode does not work with MacOS." " Running tasks in sequence.", Warning ) process = Sequential(**kwargs) - return process.get_tasks() + return process.transport() process = Parallel(**kwargs) - return process.get_tasks() + return process.transport() class Sequential(Flow): @@ -161,16 +163,18 @@ class Sequential(Flow): def setup_queue(self) -> None: self.queue = [] - def get_tasks(self) -> List[Task]: - return self.queue + def transport(self) -> List[Task]: + return self.group.tasks() def _flow_callback(self, task: Task) -> None: self.queue.append(task) def run(self) -> None: - previous_context = Context(workflow_id=self.workflow_id) + previous_context = Context( + workflow_id=self.workflow_id + ) - for task in self.tasks: + for task in self.group.tasks(): Execution( task=task, workflow_id=self.workflow_id, @@ -182,30 +186,38 @@ def run(self) -> None: key=task.config.storage.key(task=task) ) - if not self.ignore and task.status == TypeStatus.FAILED: + if not self.ignore and task.status == StatusTaskType.FAILED: break class SequentialGroup(Flow): - """SequentialGroup""" + """Sequential Group""" def setup_queue(self) -> None: - self.queue = Queue() + self.queue = None - def get_tasks(self) -> List[Task]: + if sys.version_info >= (3, 12): + self.queue = mp.Queue() + else: + self.queue = Queue() + + def transport(self) -> List[Task]: contexts = {} - while len(contexts) < len(self.tasks): - if not self.queue.empty(): - contexts = {**contexts, **self.queue.get()} + while len(contexts) < self.group.size(): + try: + data = self.queue.get(timeout=1) + contexts.update(data) + except Empty: + pass if contexts: - for task in self.tasks: + for task in self.group.tasks(): task.current_context = contexts[task.task_id]["current_context"] task.duration = contexts[task.task_id]["duration"] task.error = contexts[task.task_id]["error"] task.status = contexts[task.task_id]["status"] - return self.tasks + return self.group.tasks() def _flow_callback(self, task: Task) -> None: current_task = { @@ -222,32 +234,39 @@ def run(self) -> None: threads = [] processes = [] - for _, group_tasks in self.groups.items(): + for _, queue in self.group.queue.items(): thread = threading.Thread( target=self._launch_group, - args=(processes, group_tasks,) + args=(processes, queue,) ) thread.start() threads.append(thread) + for thread in threads: + thread.join() + for process in processes: process.join() - for thread in threads: - thread.join() + def _launch_group(self, processes, queue): + new_process = None + + if sys.version_info >= (3, 12): + new_process = mp.Process + else: + new_process = Process - def _launch_group(self, processes, group_tasks): - process = Process( + process = new_process( target=self._run_group, - args=(group_tasks,) + args=(queue,) ) process.start() processes.append(process) - def _run_group(self, groups: List[Task]) -> None: + def _run_group(self, queue: List[Task]) -> None: previous_context = Context(workflow_id=self.workflow_id) - for task in groups: + for task in queue.tasks: Execution( task=task, workflow_id=self.workflow_id, @@ -259,7 +278,7 @@ def _run_group(self, groups: List[Task]) -> None: key=task.config.storage.key(task=task) ) - if not self.ignore and task.status == TypeStatus.FAILED: + if not self.ignore and task.status == StatusTaskType.FAILED: break @@ -269,8 +288,8 @@ class Background(Flow): def setup_queue(self) -> None: self.queue = [] - def get_tasks(self) -> List[Task]: - return self.tasks + def transport(self) -> List[Task]: + return self.group.tasks() def _flow_callback(self, task: Task) -> None: pass @@ -279,10 +298,9 @@ def run(self) -> None: thread = threading.Thread( target=Sequential, args=( - self.tasks, self.workflow_id, self.ignore, - self.groups, + self.group, ), ) thread.start() @@ -293,21 +311,27 @@ class Parallel(Flow): """Parallel""" def setup_queue(self) -> None: - self.queue = Queue() + self.queue = None + + if sys.version_info >= (3, 12): + self.queue = mp.Queue() + else: + self.queue = Queue() - def get_tasks(self) -> List[Task]: + def transport(self) -> List[Task]: contexts = {} - while len(contexts) < len(self.tasks): + while len(contexts) < self.group.size(): if not self.queue.empty(): contexts = {**contexts, **self.queue.get()} - for task in self.tasks: - task.current_context = contexts[task.task_id]["current_context"] - task.duration = contexts[task.task_id]["duration"] - task.error = contexts[task.task_id]["error"] - task.status = contexts[task.task_id]["status"] + if contexts: + for task in self.group.tasks(): + task.current_context = contexts[task.task_id]["current_context"] + task.duration = contexts[task.task_id]["duration"] + task.error = contexts[task.task_id]["error"] + task.status = contexts[task.task_id]["status"] - return self.tasks + return self.group.tasks() def _flow_callback(self, task: Task) -> None: current_task = { @@ -321,13 +345,27 @@ def _flow_callback(self, task: Task) -> None: self.queue.put(current_task) def run(self) -> None: + new_process = None + + if sys.version_info >= (3, 12): + new_process = mp.Process + else: + new_process = Process + processes = [] - previous_context = Context(workflow_id=self.workflow_id) + previous_context = Context( + workflow_id=self.workflow_id + ) - for task in self.tasks: - process = Process( + for task in self.group.tasks(): + process = new_process( target=Execution, - args=(task, self.workflow_id, previous_context, self._flow_callback), + args=( + task, + self.workflow_id, + previous_context, + self._flow_callback + ), ) process.start() processes.append(process) diff --git a/dotflow/logging.py b/dotflow/logging.py index 4555948..ff19590 100644 --- a/dotflow/logging.py +++ b/dotflow/logging.py @@ -1,4 +1,4 @@ -"""Log""" +"""Logs""" import logging import logging.config diff --git a/dotflow/plugins/__init__.py b/dotflow/plugins/__init__.py new file mode 100644 index 0000000..0cb16a2 --- /dev/null +++ b/dotflow/plugins/__init__.py @@ -0,0 +1 @@ +"""Plugins __init__ module.""" diff --git a/dotflow/plugins/logs.py b/dotflow/plugins/logs.py new file mode 100644 index 0000000..f1a4535 --- /dev/null +++ b/dotflow/plugins/logs.py @@ -0,0 +1,21 @@ +"""Logs Default""" + +from dotflow.abc.logs import ( + Logs, + TYPE_LOG, + TASK_LOG_FORMAT +) +from dotflow.logging import logger + + +class LogsHandler(Logs): + + def on_task_status_change(self, task_object, type: TYPE_LOG) -> None: + new_log = getattr(logger, type.lower()) + new_log( + TASK_LOG_FORMAT.format( + task_id=task_object.task_id, + workflow_id=task_object.workflow_id, + status=task_object.status, + ) + ) diff --git a/dotflow/providers/__init__.py b/dotflow/providers/__init__.py index a8f4f3e..acce2a9 100644 --- a/dotflow/providers/__init__.py +++ b/dotflow/providers/__init__.py @@ -1,13 +1,13 @@ """Providers __init__ module.""" -from dotflow.providers.log_default import LogDefault +from dotflow.plugins.logs import LogsHandler from dotflow.providers.notify_default import NotifyDefault from dotflow.providers.notify_telegram import NotifyTelegram from dotflow.providers.storage_default import StorageDefault from dotflow.providers.storage_file import StorageFile __all__ = [ - "LogDefault", + "LogsHandler", "NotifyDefault", "NotifyTelegram", "StorageDefault", diff --git a/dotflow/providers/log_default.py b/dotflow/providers/log_default.py deleted file mode 100644 index 053406b..0000000 --- a/dotflow/providers/log_default.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Notify Default""" - -from typing import Any - -from rich.console import Console # type: ignore - -from dotflow.abc.log import Log -from dotflow.logging import logger - - -class LogDefault(Log): - - def info(self, task: Any) -> None: - logger.info( - "ID %s - %s - %s", - task.workflow_id, - task.task_id, - task.status, - ) - - def error(self, task: Any) -> None: - logger.error( - "ID %s - %s - %s \n %s", - task.workflow_id, - task.task_id, - task.status, - task.error.traceback, - ) - console = Console() - console.print_exception(show_locals=True) diff --git a/dotflow/providers/notify_telegram.py b/dotflow/providers/notify_telegram.py index d401587..2dd6cc0 100644 --- a/dotflow/providers/notify_telegram.py +++ b/dotflow/providers/notify_telegram.py @@ -5,11 +5,23 @@ from requests import post -from dotflow.core.types.status import TypeStatus +from dotflow.core.types.status import StatusTaskType from dotflow.abc.notify import Notify from dotflow.logging import logger +def get_symbol(value: str) -> str: + status = { + StatusTaskType.NOT_STARTED: "⚪", + StatusTaskType.IN_PROGRESS: "🔵", + StatusTaskType.COMPLETED: "✅", + StatusTaskType.PAUSED: "◼️", + StatusTaskType.RETRY: "❗", + StatusTaskType.FAILED: "❌" + } + return status.get(value) + + class NotifyTelegram(Notify): MESSAGE = "{symbol} {status}\n```json\n{task}```\n{workflow_id}-{task_id}" @@ -19,7 +31,7 @@ def __init__( self, token: str, chat_id: int, - notification_type: Optional[TypeStatus] = None, + notification_type: Optional[StatusTaskType] = None, timeout: int = 1.5 ): self.token = token @@ -50,7 +62,7 @@ def send(self, task: Any) -> None: def _get_text(self, task: Any) -> str: return self.MESSAGE.format( - symbol=TypeStatus.get_symbol(task.status), + symbol=get_symbol(task.status), status=task.status, workflow_id=task.workflow_id, task_id=task.task_id, diff --git a/dotflow/types.py b/dotflow/types.py index b5f320d..53a7ee3 100644 --- a/dotflow/types.py +++ b/dotflow/types.py @@ -1,9 +1,14 @@ """Types module""" -from dotflow.core.types.status import TypeStatus -from dotflow.core.types.storage import TypeStorage +from dotflow.core.types.execution import ExecutionModeType +from dotflow.core.types.status import StatusTaskType +from dotflow.core.types.storage import StorageType +from dotflow.core.types.worflow import WorkflowStatusType + __all__ = [ - "TypeStatus", - "TypeStorage" + "ExecutionModeType", + "StatusTaskType", + "StorageType", + "WorkflowStatusType" ] diff --git a/dotflow/utils/tools.py b/dotflow/utils/tools.py index 49d6011..76050e1 100644 --- a/dotflow/utils/tools.py +++ b/dotflow/utils/tools.py @@ -11,7 +11,8 @@ def write_file_system( content: str, mode: str = "w" ) -> None: - """Write file system""" + """Write file system + """ if mode == "a": system(f"echo '{content}' >> {path}") @@ -25,7 +26,8 @@ def write_file( mode: str = "w", encoding: str = "utf-8" ) -> None: - """Write file""" + """Write file + """ try: with open(file=path, mode=mode, encoding=encoding) as file: file.write(dumps(content)) @@ -38,7 +40,8 @@ def read_file( path: Path, encoding: str = "utf-8" ) -> Any: - """Read file""" + """Read file + """ if path.exists(): with open(file=path, mode="r", encoding=encoding) as file: try: @@ -46,3 +49,19 @@ def read_file( except JSONDecodeError: return file.read() return None + + +def start_and_validate_instance(current_class, instance): + """ + This function starts a class if it is not instantiated and also + performs validation that the class instance matches what is expected. + """ + if callable(current_class): + current_class = current_class() + + if not isinstance(current_class, instance): + raise ValueError( + f"Not a valid {current_class.__name__} instance class." + ) + + return current_class diff --git a/examples b/examples index ef1f9eb..ae1d54f 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit ef1f9ebf9a2f904a5027e2fe59210bfedfd0b04e +Subproject commit ae1d54fb2d653dd4b22c1ceb544808d84701577a diff --git a/mkdocs.yml b/mkdocs.yml index 44fc1c9..242c4ba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -163,8 +163,9 @@ nav: - nav/reference/utils.md - nav/reference/decorators.md - nav/reference/exception.md + - Plugin: + - nav/reference/logs-handler.md - Providers: - - nav/reference/log-default.md - nav/reference/notify-default.md - nav/reference/notify-telegram.md - nav/reference/storage-init.md @@ -175,9 +176,9 @@ nav: - Types: - nav/reference/type-execution.md - nav/reference/type-status.md - - Abstract methods: + - Abstract Methods: - nav/reference/abc-tcp.md - - nav/reference/abc-log.md + - nav/reference/abc-logs.md - nav/reference/abc-flow.md - nav/reference/abc-notify.md - nav/reference/abc-storage.md diff --git a/poetry.lock b/poetry.lock index 87b0daf..5224ecd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -18,7 +17,6 @@ version = "3.3.11" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" -groups = ["dev"] files = [ {file = "astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec"}, {file = "astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce"}, @@ -33,14 +31,13 @@ version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "backrefs" @@ -48,7 +45,6 @@ version = "5.9" description = "A wrapper around re and regex that adds additional back references." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, @@ -68,7 +64,6 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -83,7 +78,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] uv = ["uv (>=0.1.18)"] -virtualenv = ["virtualenv (>=20.11) ; python_version < \"3.10\"", "virtualenv (>=20.17) ; python_version >= \"3.10\" and python_version < \"3.14\"", "virtualenv (>=20.31) ; python_version >= \"3.14\""] +virtualenv = ["virtualenv (>=20.11)", "virtualenv (>=20.17)", "virtualenv (>=20.31)"] [[package]] name = "cachetools" @@ -91,7 +86,6 @@ version = "6.1.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e"}, {file = "cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587"}, @@ -103,7 +97,6 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "docs"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -115,8 +108,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "implementation_name == \"pypy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -196,7 +187,6 @@ version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, @@ -204,104 +194,90 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.2" +version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "docs"] -files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] @@ -310,7 +286,6 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["docs"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -325,7 +300,6 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -333,107 +307,106 @@ files = [ [[package]] name = "coverage" -version = "7.10.2" +version = "7.10.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65"}, - {file = "coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8"}, - {file = "coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87"}, - {file = "coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f"}, - {file = "coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27"}, - {file = "coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1"}, - {file = "coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce"}, - {file = "coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2"}, - {file = "coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293"}, - {file = "coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83"}, - {file = "coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b"}, - {file = "coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340"}, - {file = "coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388"}, - {file = "coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20"}, - {file = "coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186"}, - {file = "coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a"}, - {file = "coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b"}, - {file = "coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe"}, - {file = "coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7"}, - {file = "coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e"}, - {file = "coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb"}, - {file = "coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824"}, - {file = "coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3"}, - {file = "coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f"}, - {file = "coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6"}, - {file = "coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd"}, - {file = "coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8"}, - {file = "coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3"}, - {file = "coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc"}, - {file = "coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b"}, - {file = "coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed"}, - {file = "coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0"}, - {file = "coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf"}, - {file = "coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc"}, - {file = "coverage-7.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:765b13b164685a2f8b2abef867ad07aebedc0e090c757958a186f64e39d63dbd"}, - {file = "coverage-7.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a219b70100500d0c7fd3ebb824a3302efb6b1a122baa9d4eb3f43df8f0b3d899"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e33e79a219105aa315439ee051bd50b6caa705dc4164a5aba6932c8ac3ce2d98"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc3945b7bad33957a9eca16e9e5eae4b17cb03173ef594fdaad228f4fc7da53b"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bdff88e858ee608a924acfad32a180d2bf6e13e059d6a7174abbae075f30436"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44329cbed24966c0b49acb386352c9722219af1f0c80db7f218af7793d251902"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:be127f292496d0fbe20d8025f73221b36117b3587f890346e80a13b310712982"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c031da749a05f7a01447dd7f47beedb498edd293e31e1878c0d52db18787df0"}, - {file = "coverage-7.10.2-cp39-cp39-win32.whl", hash = "sha256:22aca3e691c7709c5999ccf48b7a8ff5cf5a8bd6fe9b36efbd4993f5a36b2fcf"}, - {file = "coverage-7.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7195444b932356055a8e287fa910bf9753a84a1bc33aeb3770e8fca521e032e"}, - {file = "coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f"}, - {file = "coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055"}, +files = [ + {file = "coverage-7.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d92d6edb0ccafd20c6fbf9891ca720b39c2a6a4b4a6f9cf323ca2c986f33e475"}, + {file = "coverage-7.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7202da14dc0236884fcc45665ffb2d79d4991a53fbdf152ab22f69f70923cc22"}, + {file = "coverage-7.10.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ada418633ae24ec8d0fcad5efe6fc7aa3c62497c6ed86589e57844ad04365674"}, + {file = "coverage-7.10.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b828e33eca6c3322adda3b5884456f98c435182a44917ded05005adfa1415500"}, + {file = "coverage-7.10.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:802793ba397afcfdbe9f91f89d65ae88b958d95edc8caf948e1f47d8b6b2b606"}, + {file = "coverage-7.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d0b23512338c54101d3bf7a1ab107d9d75abda1d5f69bc0887fd079253e4c27e"}, + {file = "coverage-7.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f36b7dcf72d06a8c5e2dd3aca02be2b1b5db5f86404627dff834396efce958f2"}, + {file = "coverage-7.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fce316c367a1dc2c411821365592eeb335ff1781956d87a0410eae248188ba51"}, + {file = "coverage-7.10.4-cp310-cp310-win32.whl", hash = "sha256:8c5dab29fc8070b3766b5fc85f8d89b19634584429a2da6d42da5edfadaf32ae"}, + {file = "coverage-7.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:4b0d114616f0fccb529a1817457d5fb52a10e106f86c5fb3b0bd0d45d0d69b93"}, + {file = "coverage-7.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:05d5f98ec893d4a2abc8bc5f046f2f4367404e7e5d5d18b83de8fde1093ebc4f"}, + {file = "coverage-7.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9267efd28f8994b750d171e58e481e3bbd69e44baed540e4c789f8e368b24b88"}, + {file = "coverage-7.10.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4456a039fdc1a89ea60823d0330f1ac6f97b0dbe9e2b6fb4873e889584b085fb"}, + {file = "coverage-7.10.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c2bfbd2a9f7e68a21c5bd191be94bfdb2691ac40d325bac9ef3ae45ff5c753d9"}, + {file = "coverage-7.10.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab7765f10ae1df7e7fe37de9e64b5a269b812ee22e2da3f84f97b1c7732a0d8"}, + {file = "coverage-7.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a09b13695166236e171ec1627ff8434b9a9bae47528d0ba9d944c912d33b3d2"}, + {file = "coverage-7.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5c9e75dfdc0167d5675e9804f04a56b2cf47fb83a524654297000b578b8adcb7"}, + {file = "coverage-7.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c751261bfe6481caba15ec005a194cb60aad06f29235a74c24f18546d8377df0"}, + {file = "coverage-7.10.4-cp311-cp311-win32.whl", hash = "sha256:051c7c9e765f003c2ff6e8c81ccea28a70fb5b0142671e4e3ede7cebd45c80af"}, + {file = "coverage-7.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a647b152f10be08fb771ae4a1421dbff66141e3d8ab27d543b5eb9ea5af8e52"}, + {file = "coverage-7.10.4-cp311-cp311-win_arm64.whl", hash = "sha256:b09b9e4e1de0d406ca9f19a371c2beefe3193b542f64a6dd40cfcf435b7d6aa0"}, + {file = "coverage-7.10.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1f0264abcabd4853d4cb9b3d164adbf1565da7dab1da1669e93f3ea60162d79"}, + {file = "coverage-7.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:536cbe6b118a4df231b11af3e0f974a72a095182ff8ec5f4868c931e8043ef3e"}, + {file = "coverage-7.10.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9a4c0d84134797b7bf3f080599d0cd501471f6c98b715405166860d79cfaa97e"}, + {file = "coverage-7.10.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c155fc0f9cee8c9803ea0ad153ab6a3b956baa5d4cd993405dc0b45b2a0b9e0"}, + {file = "coverage-7.10.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5f2ab6e451d4b07855d8bcf063adf11e199bff421a4ba57f5bb95b7444ca62"}, + {file = "coverage-7.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:685b67d99b945b0c221be0780c336b303a7753b3e0ec0d618c795aada25d5e7a"}, + {file = "coverage-7.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c079027e50c2ae44da51c2e294596cbc9dbb58f7ca45b30651c7e411060fc23"}, + {file = "coverage-7.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3749aa72b93ce516f77cf5034d8e3c0dfd45c6e8a163a602ede2dc5f9a0bb927"}, + {file = "coverage-7.10.4-cp312-cp312-win32.whl", hash = "sha256:fecb97b3a52fa9bcd5a7375e72fae209088faf671d39fae67261f37772d5559a"}, + {file = "coverage-7.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:26de58f355626628a21fe6a70e1e1fad95702dafebfb0685280962ae1449f17b"}, + {file = "coverage-7.10.4-cp312-cp312-win_arm64.whl", hash = "sha256:67e8885408f8325198862bc487038a4980c9277d753cb8812510927f2176437a"}, + {file = "coverage-7.10.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b8e1d2015d5dfdbf964ecef12944c0c8c55b885bb5c0467ae8ef55e0e151233"}, + {file = "coverage-7.10.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:25735c299439018d66eb2dccf54f625aceb78645687a05f9f848f6e6c751e169"}, + {file = "coverage-7.10.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:715c06cb5eceac4d9b7cdf783ce04aa495f6aff657543fea75c30215b28ddb74"}, + {file = "coverage-7.10.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e017ac69fac9aacd7df6dc464c05833e834dc5b00c914d7af9a5249fcccf07ef"}, + {file = "coverage-7.10.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bad180cc40b3fccb0f0e8c702d781492654ac2580d468e3ffc8065e38c6c2408"}, + {file = "coverage-7.10.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:becbdcd14f685fada010a5f792bf0895675ecf7481304fe159f0cd3f289550bd"}, + {file = "coverage-7.10.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0b485ca21e16a76f68060911f97ebbe3e0d891da1dbbce6af7ca1ab3f98b9097"}, + {file = "coverage-7.10.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d098ccfe8e1e0a1ed9a0249138899948afd2978cbf48eb1cc3fcd38469690"}, + {file = "coverage-7.10.4-cp313-cp313-win32.whl", hash = "sha256:8630f8af2ca84b5c367c3df907b1706621abe06d6929f5045fd628968d421e6e"}, + {file = "coverage-7.10.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68835d31c421736be367d32f179e14ca932978293fe1b4c7a6a49b555dff5b2"}, + {file = "coverage-7.10.4-cp313-cp313-win_arm64.whl", hash = "sha256:6eaa61ff6724ca7ebc5326d1fae062d85e19b38dd922d50903702e6078370ae7"}, + {file = "coverage-7.10.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:702978108876bfb3d997604930b05fe769462cc3000150b0e607b7b444f2fd84"}, + {file = "coverage-7.10.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8f978e8c5521d9c8f2086ac60d931d583fab0a16f382f6eb89453fe998e2484"}, + {file = "coverage-7.10.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:df0ac2ccfd19351411c45e43ab60932b74472e4648b0a9edf6a3b58846e246a9"}, + {file = "coverage-7.10.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73a0d1aaaa3796179f336448e1576a3de6fc95ff4f07c2d7251d4caf5d18cf8d"}, + {file = "coverage-7.10.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873da6d0ed6b3ffc0bc01f2c7e3ad7e2023751c0d8d86c26fe7322c314b031dc"}, + {file = "coverage-7.10.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c6446c75b0e7dda5daa876a1c87b480b2b52affb972fedd6c22edf1aaf2e00ec"}, + {file = "coverage-7.10.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6e73933e296634e520390c44758d553d3b573b321608118363e52113790633b9"}, + {file = "coverage-7.10.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52073d4b08d2cb571234c8a71eb32af3c6923149cf644a51d5957ac128cf6aa4"}, + {file = "coverage-7.10.4-cp313-cp313t-win32.whl", hash = "sha256:e24afb178f21f9ceb1aefbc73eb524769aa9b504a42b26857243f881af56880c"}, + {file = "coverage-7.10.4-cp313-cp313t-win_amd64.whl", hash = "sha256:be04507ff1ad206f4be3d156a674e3fb84bbb751ea1b23b142979ac9eebaa15f"}, + {file = "coverage-7.10.4-cp313-cp313t-win_arm64.whl", hash = "sha256:f3e3ff3f69d02b5dad67a6eac68cc9c71ae343b6328aae96e914f9f2f23a22e2"}, + {file = "coverage-7.10.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a59fe0af7dd7211ba595cf7e2867458381f7e5d7b4cffe46274e0b2f5b9f4eb4"}, + {file = "coverage-7.10.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a6c35c5b70f569ee38dc3350cd14fdd0347a8b389a18bb37538cc43e6f730e6"}, + {file = "coverage-7.10.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:acb7baf49f513554c4af6ef8e2bd6e8ac74e6ea0c7386df8b3eb586d82ccccc4"}, + {file = "coverage-7.10.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a89afecec1ed12ac13ed203238b560cbfad3522bae37d91c102e690b8b1dc46c"}, + {file = "coverage-7.10.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:480442727f464407d8ade6e677b7f21f3b96a9838ab541b9a28ce9e44123c14e"}, + {file = "coverage-7.10.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a89bf193707f4a17f1ed461504031074d87f035153239f16ce86dfb8f8c7ac76"}, + {file = "coverage-7.10.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3ddd912c2fc440f0fb3229e764feec85669d5d80a988ff1b336a27d73f63c818"}, + {file = "coverage-7.10.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a538944ee3a42265e61c7298aeba9ea43f31c01271cf028f437a7b4075592cf"}, + {file = "coverage-7.10.4-cp314-cp314-win32.whl", hash = "sha256:fd2e6002be1c62476eb862b8514b1ba7e7684c50165f2a8d389e77da6c9a2ebd"}, + {file = "coverage-7.10.4-cp314-cp314-win_amd64.whl", hash = "sha256:ec113277f2b5cf188d95fb66a65c7431f2b9192ee7e6ec9b72b30bbfb53c244a"}, + {file = "coverage-7.10.4-cp314-cp314-win_arm64.whl", hash = "sha256:9744954bfd387796c6a091b50d55ca7cac3d08767795b5eec69ad0f7dbf12d38"}, + {file = "coverage-7.10.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5af4829904dda6aabb54a23879f0f4412094ba9ef153aaa464e3c1b1c9bc98e6"}, + {file = "coverage-7.10.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7bba5ed85e034831fac761ae506c0644d24fd5594727e174b5a73aff343a7508"}, + {file = "coverage-7.10.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d57d555b0719834b55ad35045de6cc80fc2b28e05adb6b03c98479f9553b387f"}, + {file = "coverage-7.10.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ba62c51a72048bb1ea72db265e6bd8beaabf9809cd2125bbb5306c6ce105f214"}, + {file = "coverage-7.10.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0acf0c62a6095f07e9db4ec365cc58c0ef5babb757e54745a1aa2ea2a2564af1"}, + {file = "coverage-7.10.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1033bf0f763f5cf49ffe6594314b11027dcc1073ac590b415ea93463466deec"}, + {file = "coverage-7.10.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:92c29eff894832b6a40da1789b1f252305af921750b03ee4535919db9179453d"}, + {file = "coverage-7.10.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:822c4c830989c2093527e92acd97be4638a44eb042b1bdc0e7a278d84a070bd3"}, + {file = "coverage-7.10.4-cp314-cp314t-win32.whl", hash = "sha256:e694d855dac2e7cf194ba33653e4ba7aad7267a802a7b3fc4347d0517d5d65cd"}, + {file = "coverage-7.10.4-cp314-cp314t-win_amd64.whl", hash = "sha256:efcc54b38ef7d5bfa98050f220b415bc5bb3d432bd6350a861cf6da0ede2cdcd"}, + {file = "coverage-7.10.4-cp314-cp314t-win_arm64.whl", hash = "sha256:6f3a3496c0fa26bfac4ebc458747b778cff201c8ae94fa05e1391bab0dbc473c"}, + {file = "coverage-7.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48fd4d52600c2a9d5622e52dfae674a7845c5e1dceaf68b88c99feb511fbcfd6"}, + {file = "coverage-7.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:56217b470d09d69e6b7dcae38200f95e389a77db801cb129101697a4553b18b6"}, + {file = "coverage-7.10.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:44ac3f21a6e28c5ff7f7a47bca5f87885f6a1e623e637899125ba47acd87334d"}, + {file = "coverage-7.10.4-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3387739d72c84d17b4d2f7348749cac2e6700e7152026912b60998ee9a40066b"}, + {file = "coverage-7.10.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f111ff20d9a6348e0125be892608e33408dd268f73b020940dfa8511ad05503"}, + {file = "coverage-7.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:01a852f0a9859734b018a3f483cc962d0b381d48d350b1a0c47d618c73a0c398"}, + {file = "coverage-7.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:225111dd06759ba4e37cee4c0b4f3df2b15c879e9e3c37bf986389300b9917c3"}, + {file = "coverage-7.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2178d4183bd1ba608f0bb12e71e55838ba1b7dbb730264f8b08de9f8ef0c27d0"}, + {file = "coverage-7.10.4-cp39-cp39-win32.whl", hash = "sha256:93d175fe81913aee7a6ea430abbdf2a79f1d9fd451610e12e334e4fe3264f563"}, + {file = "coverage-7.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:2221a823404bb941c7721cf0ef55ac6ee5c25d905beb60c0bba5e5e85415d353"}, + {file = "coverage-7.10.4-py3-none-any.whl", hash = "sha256:065d75447228d05121e5c938ca8f0e91eed60a1eb2d1258d42d5084fecfc3302"}, + {file = "coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "cyclic" @@ -441,7 +414,6 @@ version = "1.0.0" description = "Handle cyclic relations" optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "cyclic-1.0.0-py3-none-any.whl", hash = "sha256:32d8181d7698f426bce6f14f4c3921ef95b6a84af9f96192b59beb05bc00c3ed"}, {file = "cyclic-1.0.0.tar.gz", hash = "sha256:ecddd56cb831ee3e6b79f61ecb0ad71caee606c507136867782911aa01c3e5eb"}, @@ -453,7 +425,6 @@ version = "0.4.0" description = "serialize all of Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, @@ -469,59 +440,17 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[[package]] -name = "dnspython" -version = "2.7.0" -description = "DNS toolkit" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"mongodb\"" -files = [ - {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, - {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=43)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=1.0.0)"] -idna = ["idna (>=3.7)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "dotflow-mongodb" -version = "0.1.2" -description = "Dotflow Storage MongoDB" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"mongodb\"" -files = [ - {file = "dotflow_mongodb-0.1.2-py3-none-any.whl", hash = "sha256:cc2d1de05939bf333e1aa88f8b8d3998beaa926241ecb6c85d86311e1d234a6d"}, - {file = "dotflow_mongodb-0.1.2.tar.gz", hash = "sha256:1a6451b649ba843da8ea5379199f18b4f2751e9b3d877a45ae8894cfa6171870"}, -] - -[package.dependencies] -dotflow = "*" -pymongo = "*" - [[package]] name = "exceptiongroup" version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -535,28 +464,21 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.18.0" +version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] - [[package]] name = "flake8" version = "7.3.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, @@ -573,7 +495,6 @@ version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -587,14 +508,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "griffe" -version = "1.9.0" +version = "1.12.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ - {file = "griffe-1.9.0-py3-none-any.whl", hash = "sha256:bcf90ee3ad42bbae70a2a490c782fc8e443de9b84aa089d857c278a4e23215fc"}, - {file = "griffe-1.9.0.tar.gz", hash = "sha256:b5531cf45e9b73f0842c2121cc4d4bcbb98a55475e191fc9830e7aef87a920a0"}, + {file = "griffe-1.12.1-py3-none-any.whl", hash = "sha256:2d7c12334de00089c31905424a00abcfd931b45b8b516967f224133903d302cc"}, + {file = "griffe-1.12.1.tar.gz", hash = "sha256:29f5a6114c0aeda7d9c86a570f736883f8a2c5b38b57323d56b3d1c000565567"}, ] [package.dependencies] @@ -606,7 +526,6 @@ version = "0.2.8" description = "Griffe extension for PEP 727 – Documentation Metadata in Typing." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "griffe_typingdoc-0.2.8-py3-none-any.whl", hash = "sha256:a4ed3dd73b9d48311b138d8b317916a0589325a73c525236bf5969a8fe2626b1"}, {file = "griffe_typingdoc-0.2.8.tar.gz", hash = "sha256:36f2c2f2568240a5d0ab462153d1f3cfec01a9cc56b2291f16ce7869f0f7af05"}, @@ -622,7 +541,6 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "docs"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -637,23 +555,21 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, ] -markers = {dev = "python_full_version < \"3.10.2\""} [package.dependencies] zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -662,7 +578,6 @@ version = "6.5.2" description = "Read resources from Python packages" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, @@ -672,7 +587,7 @@ files = [ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -685,7 +600,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -697,7 +611,6 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["dev"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -713,7 +626,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -731,7 +643,6 @@ version = "3.8.2" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, @@ -750,7 +661,6 @@ version = "0.0.4" description = "Markdown extension to expand directives to include source example files to also include their variants. Only useful to tiangolo's projets. Don't use it. 😅" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "markdown_include_variants-0.0.4-py3-none-any.whl", hash = "sha256:f940b3978ca63d05118a261ff653d54d1d325120de41da9b901ccf4a85f1da1c"}, {file = "markdown_include_variants-0.0.4.tar.gz", hash = "sha256:8207c8a451eb79f10973d10ef5745b619bc51e245e4c4e5f0556a7a05243bea7"}, @@ -765,7 +675,6 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -790,7 +699,6 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -861,7 +769,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -873,7 +780,6 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -885,7 +791,6 @@ version = "1.4.2" description = "Python Markdown extension to include local or remote files" optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "mdx_include-1.4.2-py3-none-any.whl", hash = "sha256:cfbeadd59985f27a9b70cb7ab0a3d209892fe1bb1aa342df055e0b135b3c9f34"}, {file = "mdx_include-1.4.2.tar.gz", hash = "sha256:992f9fbc492b5cf43f7d8cb4b90b52a4e4c5fdd7fd04570290a83eea5c84f297"}, @@ -902,7 +807,6 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" -groups = ["docs"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -914,7 +818,6 @@ version = "2.1.3" description = "Manage multiple versions of your MkDocs-powered documentation" optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a"}, {file = "mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810"}, @@ -940,7 +843,6 @@ version = "1.6.1" description = "Project documentation with Markdown." optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -964,7 +866,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -972,7 +874,6 @@ version = "1.4.2" description = "Automatically link across pages in MkDocs." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13"}, {file = "mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749"}, @@ -989,7 +890,6 @@ version = "0.2.0" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, @@ -1003,19 +903,19 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.6.16" +version = "9.6.17" description = "Documentation that simply works" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ - {file = "mkdocs_material-9.6.16-py3-none-any.whl", hash = "sha256:8d1a1282b892fe1fdf77bfeb08c485ba3909dd743c9ba69a19a40f637c6ec18c"}, - {file = "mkdocs_material-9.6.16.tar.gz", hash = "sha256:d07011df4a5c02ee0877496d9f1bfc986cfb93d964799b032dd99fe34c0e9d19"}, + {file = "mkdocs_material-9.6.17-py3-none-any.whl", hash = "sha256:221dd8b37a63f52e580bcab4a7e0290e4a6f59bd66190be9c3d40767e05f9417"}, + {file = "mkdocs_material-9.6.17.tar.gz", hash = "sha256:48ae7aec72a3f9f501a70be3fbd329c96ff5f5a385b67a1563e5ed5ce064affe"}, ] [package.dependencies] babel = ">=2.10,<3.0" backrefs = ">=5.7.post1,<6.0" +click = "<8.2.2" colorama = ">=0.4,<1.0" jinja2 = ">=3.1,<4.0" markdown = ">=3.2,<4.0" @@ -1037,7 +937,6 @@ version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, @@ -1049,7 +948,6 @@ version = "0.2.0" description = "Mkdocs Blog Theme" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "mkdocs_simple_blog-0.2.0-py3-none-any.whl", hash = "sha256:ffa09798ca598c67c260e6f588f63d7dd5a1e50eeb632644e4952fee8116d447"}, {file = "mkdocs_simple_blog-0.2.0.tar.gz", hash = "sha256:62f7ad2594566020f97fa614002ec71bc97e7fb38060341b2c2ccd9ca2cb1fe0"}, @@ -1064,7 +962,6 @@ version = "1.3.0" description = "MkDocs i18n plugin using static translation markdown files" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "mkdocs_static_i18n-1.3.0-py3-none-any.whl", hash = "sha256:7905d52fff71d2c108b6c344fd223e848ca7e39ddf319b70864dfa47dba85d6b"}, {file = "mkdocs_static_i18n-1.3.0.tar.gz", hash = "sha256:65731e1e4ec6d719693e24fee9340f5516460b2b7244d2a89bed4ce3cfa6a173"}, @@ -1082,7 +979,6 @@ version = "0.29.1" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "mkdocstrings-0.29.1-py3-none-any.whl", hash = "sha256:37a9736134934eea89cbd055a513d40a020d87dfcae9e3052c2a6b8cd4af09b6"}, {file = "mkdocstrings-0.29.1.tar.gz", hash = "sha256:8722f8f8c5cd75da56671e0a0c1bbed1df9946c0cef74794d6141b34011abd42"}, @@ -1109,7 +1005,6 @@ version = "1.16.12" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374"}, {file = "mkdocstrings_python-1.16.12.tar.gz", hash = "sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d"}, @@ -1127,7 +1022,6 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1139,7 +1033,6 @@ version = "0.5.7" description = "Divides large result sets into pages for easier browsing" optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, @@ -1155,7 +1048,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1167,7 +1059,6 @@ version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -1184,7 +1075,6 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -1200,7 +1090,6 @@ version = "2.14.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, @@ -1212,8 +1101,6 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "implementation_name == \"pypy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1225,7 +1112,6 @@ version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, @@ -1239,7 +1125,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -1247,7 +1133,6 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -1359,7 +1244,6 @@ version = "3.4.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, @@ -1371,7 +1255,6 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1382,14 +1265,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "3.3.7" +version = "3.3.8" description = "python code static checker" optional = false python-versions = ">=3.9.0" -groups = ["dev"] files = [ - {file = "pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d"}, - {file = "pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559"}, + {file = "pylint-3.3.8-py3-none-any.whl", hash = "sha256:7ef94aa692a600e82fabdd17102b73fc226758218c97473c7ad67bd4cb905d83"}, + {file = "pylint-3.3.8.tar.gz", hash = "sha256:26698de19941363037e2937d3db9ed94fb3303fdadf7d98847875345a8bb6b05"}, ] [package.dependencies] @@ -1398,7 +1280,7 @@ colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version == \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13 || >5.13,<7" mccabe = ">=0.6,<0.8" @@ -1417,7 +1299,6 @@ version = "10.16.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, @@ -1430,94 +1311,12 @@ pyyaml = "*" [package.extras] extra = ["pygments (>=2.19.1)"] -[[package]] -name = "pymongo" -version = "4.14.0" -description = "PyMongo - the Official MongoDB Python driver" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"mongodb\"" -files = [ - {file = "pymongo-4.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8f63035da92f923d966fb07017969b5c42d0450412ceb6c3f837a02b72a5a69d"}, - {file = "pymongo-4.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7e6b1f34db93856b083760fe2848562c0b39433fc1a0827155ea02fee5733c64"}, - {file = "pymongo-4.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5af18f9239e672c2b9713a8e5f0267e927c9dcc3960dce9599712f304fd377f"}, - {file = "pymongo-4.14.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a58339aeb2e0f4e347778a5b5130acca0384c0146135e1afe7bf2b9a2701"}, - {file = "pymongo-4.14.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23d48bf539f581603a7425e4eeaea279bdb89caa0462aab29e163c11c5d02a9c"}, - {file = "pymongo-4.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4220f42630b260c2594589202efaa4916f3799e87c9fdc479fb6ed62457c74fe"}, - {file = "pymongo-4.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7e34bd3fe6e5034e22d47d685391ae0f2f2b22d150e9a36a6b77f0c0234e21d"}, - {file = "pymongo-4.14.0-cp310-cp310-win32.whl", hash = "sha256:f4712911b6dc07aaa00aaa87749b63370ca5900714a2b4b19ba1385163e90295"}, - {file = "pymongo-4.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:74fb430c98c5a7f871bfa40f77c8158af8bde256215296d7cfe1ec9c5a417718"}, - {file = "pymongo-4.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88f9c415c59450c0ac4133aa4745459101619ca7997dc468209bf395563667d2"}, - {file = "pymongo-4.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6513474970fdf3afd9dc9de9065a31a1efc8288ca9068510e5e973fa80200c8f"}, - {file = "pymongo-4.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d60c0e9b13603317062f316906fb5be4000f5b5fe288eb6e9df4ef8695863cd8"}, - {file = "pymongo-4.14.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a203cb757f75804c43aec48f23cb138e890a24219716ce9958041dace39ba470"}, - {file = "pymongo-4.14.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bdf65bf8167a92b70de44d28ed9df1c2dec83fe2a82e26c01fc89da8ca6bc34"}, - {file = "pymongo-4.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f79fdd99db135dbc1678477793764c156c11623d0d9dbe4c57767d081b79b8"}, - {file = "pymongo-4.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05152a2ca55805c37f77ac473e51915f44bba9a6b32fed875fa9df61d81681ca"}, - {file = "pymongo-4.14.0-cp311-cp311-win32.whl", hash = "sha256:aa25505e36e32bef3fa135578461f24735e9d4b7b62e6aa21eb8f2d163cef86d"}, - {file = "pymongo-4.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57aef3b48e8c7818689604ff24e54524e164056ec56ee5ea48384264360bf59"}, - {file = "pymongo-4.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:864f005459ef9b19c60cb96ca755124e0291d211000859f33c62a166f55eba27"}, - {file = "pymongo-4.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69956f971a6f8dafc62e5c83ac21ebf15d5757e13758284f12218ad7fbd3c0fe"}, - {file = "pymongo-4.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91200f47453a1cb97136629e28f4091a109756ec37067b622f90c4b626b4af8d"}, - {file = "pymongo-4.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c32f5e23e9dd31e20800d993f3695b6627664dc7da30ac1669f9716833b33175"}, - {file = "pymongo-4.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4407c1ab7514e08d4af0f58cf9d7eddc86e45e458fe46f99a72a5d18dbc71dc"}, - {file = "pymongo-4.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ec6666e599ee10cc5cde0cc6a8519373384a14af3a310ede1bf177105f38fb0"}, - {file = "pymongo-4.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a809a982a843bb561c7030059d54ea7f1dcc967cc72a45f1435695e2a2a515a5"}, - {file = "pymongo-4.14.0-cp312-cp312-win32.whl", hash = "sha256:3866d031fcbe81d7677c078026e650aeef8915560ba758a28051debce38f6b77"}, - {file = "pymongo-4.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:91b8de871a40225bbe4f92d6dc3f20c26bf838e49d3563592131401af0d665a6"}, - {file = "pymongo-4.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54576faf8e6fefe17886a201f9df61bdf728ccac9a7a0847095a0e8480cd6ec1"}, - {file = "pymongo-4.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c64ef5e58adedecb853a680eb5d1aea50770197f212e202d6eb50c801797b576"}, - {file = "pymongo-4.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f1c0cdddf9783065bf55d3fe025843c0974a828bafc9bb5514ae28dd2828a40"}, - {file = "pymongo-4.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22805d4fa587b526ac3129ee634a8761abbeb76883045718438f5b8e72f91ce6"}, - {file = "pymongo-4.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c3e6e669cf36b27694de2730f5d5b31ef492ffe99446563192c4e8ee84ca859"}, - {file = "pymongo-4.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9763a2477d5388df65ab6f591cf0cb7fd34f4a66f873c31e63288fd79887742c"}, - {file = "pymongo-4.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d6f5aceab3528b8760942a57635599925f7fd743691f9d759a02124207dfd0"}, - {file = "pymongo-4.14.0-cp313-cp313-win32.whl", hash = "sha256:e283feafde118cbbb03adc036b882be042b0a2eca121ec5d6bbec3e12980e8fa"}, - {file = "pymongo-4.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:a29b62d421a512833e99d4781b64e695cfe23b4c4a9159ea83e56fc2660f2480"}, - {file = "pymongo-4.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67225d5fb1be2a34c6f73cde9d81a8041a095a94ed2433e2cf9e2f1657443def"}, - {file = "pymongo-4.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bbf0dbe8554978c1271bdc82366852245a03ab124a1387b6f3531f64adac3c39"}, - {file = "pymongo-4.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7f61561cbc7426ffc2d37b46e65ab5323fc366644f70c8e2240ed5452e2c402"}, - {file = "pymongo-4.14.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:502cd551679753fb7838221c3bbb963da4b4aa0576848192afb1f78128ff729a"}, - {file = "pymongo-4.14.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba6b59afef2b47c4859bf36115fa577330601b93e39d04f39fcc6103e801286"}, - {file = "pymongo-4.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c0fa103f978c15f7c2f0d7b2e010c24c327432a0310503bc0ec93c5f9be9e81"}, - {file = "pymongo-4.14.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe93676afe37794f01b8df5cf16528dce4d7d174cdf51ea1586c234eb5263c2"}, - {file = "pymongo-4.14.0-cp313-cp313t-win32.whl", hash = "sha256:1462fc2bb39527f01eea5378172b66c45d62e22fa4be957afe2ec747c4d2ff51"}, - {file = "pymongo-4.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e506af9b25aac77cc5c5ea4a72f81764e4f5ea90ca799aac43d665ab269f291d"}, - {file = "pymongo-4.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:66c70cea48a6d8eba16f7435033fe3f948335132f807a85e9d861f908ea43fe7"}, - {file = "pymongo-4.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f8874b1ac78b204a3df69351b3c594971be91dba80f7a9189b95097595601223"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e1a670892b0435456bbc068e244c71ae3dc0994f7abdf5a0b06db73e32dd86"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e08f4dcd8f23f7ea2c9ae1bb5a043d9b77dfbf03fc489b7e1de91422b0c4772a"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1914e06a443db7e32ddb992050983910bd6861e690200005d7eb1418ad2dc4be"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a6915825716edc65239b5658b7de6a09af5d30517706122df66579ddba9349"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fa071c00fd5556b79c0d94d95b2b778d70ff4ea701aeecb63dd5353d7e025f7"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f51b4885ed197a874272f1bff99d15d0c1f35c665d86b9a2a6572a10246af974"}, - {file = "pymongo-4.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7698c8cf749988b7fd259ea65a5fcd409ebde1c6bc1bf75ab7fadbfbd154a3a4"}, - {file = "pymongo-4.14.0-cp39-cp39-win32.whl", hash = "sha256:85d8d145e06916db46b1e25f39b5361fdf62d58e127b1b1652a0df4ea8081b2f"}, - {file = "pymongo-4.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:ee0e677b2139c5b560b63d745a5f26d2e985f624848132deb366c6d3322836bb"}, - {file = "pymongo-4.14.0.tar.gz", hash = "sha256:15674e3fddce78cf134fc4e55f90abf1608a48430130cd35efdf3802fd47a1d1"}, -] - -[package.dependencies] -dnspython = ">=1.16.0,<3.0.0" - -[package.extras] -aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] -docs = ["furo (==2025.7.19)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] -encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] -gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] -ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] -snappy = ["python-snappy"] -test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] -zstd = ["zstandard"] - [[package]] name = "pyparsing" version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, @@ -1532,7 +1331,6 @@ version = "1.9.1" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948"}, {file = "pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335"}, @@ -1552,7 +1350,6 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -1564,7 +1361,6 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -1588,7 +1384,6 @@ version = "6.2.1" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, @@ -1608,7 +1403,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["docs"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1617,13 +1411,26 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.1.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +files = [ + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["docs"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1686,7 +1493,6 @@ version = "1.1" description = "A custom YAML tag for referencing environment variables in YAML files." optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, @@ -1701,7 +1507,6 @@ version = "26.4.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918"}, {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315"}, @@ -1807,7 +1612,6 @@ version = "1.1.0" description = "Slice a list of sliceables (1 indexed, start and end index both are inclusive)" optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "rcslice-1.1.0-py3-none-any.whl", hash = "sha256:1b12fc0c0ca452e8a9fd2b56ac008162f19e250906a4290a7e7a98be3200c2a6"}, {file = "rcslice-1.1.0.tar.gz", hash = "sha256:a2ce70a60690eb63e52b722e046b334c3aaec5e900b28578f529878782ee5c6e"}, @@ -1819,7 +1623,6 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "docs"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -1841,7 +1644,6 @@ version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["main"] files = [ {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, @@ -1860,7 +1662,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["docs"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1872,8 +1673,6 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1915,7 +1714,6 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -1927,7 +1725,6 @@ version = "4.28.4" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "tox-4.28.4-py3-none-any.whl", hash = "sha256:8d4ad9ee916ebbb59272bb045e154a10fa12e3bbdcf94cc5185cbdaf9b241f99"}, {file = "tox-4.28.4.tar.gz", hash = "sha256:b5b14c6307bd8994ff1eba5074275826620325ee1a4f61316959d562bfd70b9d"}, @@ -1952,12 +1749,10 @@ version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] -markers = {dev = "python_version < \"3.11\""} [[package]] name = "typing-inspection" @@ -1965,7 +1760,6 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -1980,14 +1774,13 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "docs"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1998,7 +1791,6 @@ version = "0.1.0" description = "Flexible version handling" optional = false python-versions = "*" -groups = ["docs"] files = [ {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, @@ -2009,24 +1801,24 @@ test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] [[package]] name = "virtualenv" -version = "20.33.0" +version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "virtualenv-20.33.0-py3-none-any.whl", hash = "sha256:106b6baa8ab1b526d5a9b71165c85c456fbd49b16976c88e2bc9352ee3bc5d3f"}, - {file = "virtualenv-20.33.0.tar.gz", hash = "sha256:47e0c0d2ef1801fce721708ccdf2a28b9403fa2307c3268aebd03225976f61d2"}, + {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, + {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "watchdog" @@ -2034,7 +1826,6 @@ version = "6.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" -groups = ["docs"] files = [ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, @@ -2077,25 +1868,20 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] -markers = {dev = "python_full_version < \"3.10.2\""} [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] -[extras] -mongodb = ["dotflow-mongodb"] - [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.9.0" -content-hash = "aee92d8a8ed56c547f089ff0e338ab2591274a62d59e019b8d4485ebd949a853" +content-hash = "16bb43f1e4c9b2fbce58818fcde3d08869e63b78b70868313949bb785daa244b" diff --git a/requirements.txt b/requirements.txt index f557d2d..aa614b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,15 @@ -rich -pydantic -typing-extensions \ No newline at end of file +annotated-types==0.7.0 ; python_version >= "3.9" +certifi==2025.8.3 ; python_full_version >= "3.9.0" +charset-normalizer==3.4.3 ; python_full_version >= "3.9.0" +idna==3.10 ; python_full_version >= "3.9.0" +markdown-it-py==3.0.0 ; python_full_version >= "3.9.0" +mdurl==0.1.2 ; python_full_version >= "3.9.0" +pydantic-core==2.33.2 ; python_version >= "3.9" +pydantic==2.11.7 ; python_version >= "3.9" +pygments==2.19.2 ; python_full_version >= "3.9.0" +python-dotenv==1.1.1 ; python_version >= "3.9" +requests==2.32.4 ; python_full_version >= "3.9.0" +rich==14.1.0 ; python_full_version >= "3.9.0" +typing-extensions==4.14.1 ; python_version >= "3.9" +typing-inspection==0.4.1 ; python_version >= "3.9" +urllib3==2.5.0 ; python_version >= "3.9" diff --git a/tests/core/test_config.py b/tests/core/test_config.py new file mode 100644 index 0000000..9b430f0 --- /dev/null +++ b/tests/core/test_config.py @@ -0,0 +1,44 @@ +"""Test of config""" + +import unittest + +from dotflow.core.config import Config, ConfigInstance +from dotflow.abc.storage import Storage +from dotflow.abc.notify import Notify +from dotflow.abc.logs import Logs + + +class TestConfigInstance(unittest.TestCase): + + def setUp(self): + self.instance = ConfigInstance() + + def test_instantiating_config_instance_class(self): + self.assertIsNone(self.instance._storage) + self.assertIsNone(self.instance._notify) + self.assertIsNone(self.instance._logs) + + +class TestConfig(unittest.TestCase): + + def setUp(self): + self.config = Config() + self.index = 0 + + def test_instantiating_config_class(self): + self.assertIsInstance(self.config.storage, Storage) + self.assertIsInstance(self.config.notify, Notify) + self.assertIsInstance(self.config.logs, list) + self.assertIsInstance(self.config.logs[self.index], Logs) + + def test_config_storage_object(self): + self.assertIsNotNone(self.config.storage) + self.assertFalse(callable(self.config.storage)) + + def test_config_notify_object(self): + self.assertIsNotNone(self.config.notify) + self.assertFalse(callable(self.config.notify)) + + def test_config_logs_object(self): + self.assertIsNotNone(self.config.logs) + self.assertFalse(callable(self.config.logs[self.index])) diff --git a/tests/core/test_dotflow.py b/tests/core/test_dotflow.py index 19fb404..4e1fe6c 100644 --- a/tests/core/test_dotflow.py +++ b/tests/core/test_dotflow.py @@ -1,63 +1,103 @@ -"""Test context of workflow""" +"""Test of dotflow""" import unittest +from uuid import UUID + +from dotflow.core.config import Config from dotflow.core.workflow import Manager from dotflow.core.context import Context from dotflow.core.task import Task, TaskBuilder -from dotflow.core.dotflow import DotFlow -from dotflow.core.types.status import TypeStatus +from dotflow.core.dotflow import DotFlow, DotflowInstance +from dotflow.core.types.status import StatusTaskType from tests.mocks import action_step +class TestDotflowInstance(unittest.TestCase): + + def setUp(self): + self.instance = DotflowInstance() + + def test_instantiating_dotflow_instance_class(self): + self.assertIsNone(self.instance._config) + self.assertIsNone(self.instance._workflow_id) + self.assertIsNone(self.instance._task) + self.assertIsNone(self.instance._add) + self.assertIsNone(self.instance._start) + + class TestDotFlow(unittest.TestCase): def setUp(self): - self.workflow = DotFlow() - self.workflow.task.add(step=action_step) + self.dotflow = DotFlow() + self.dotflow.add(step=action_step) + self.index = 0 + self.size = 1 def test_instantiating_dotflow_class(self): - self.assertIsInstance(self.workflow.task, TaskBuilder) - self.assertIsInstance(self.workflow.start(), Manager) + self.assertIsInstance(self.dotflow.config, Config) + self.assertIsInstance(self.dotflow.workflow_id, UUID) + self.assertIsInstance(self.dotflow.task, TaskBuilder) + self.assertIsInstance(self.dotflow.start(), Manager) def test_result_task_with_start(self): - self.workflow.start() - result = self.workflow.result_task() + self.dotflow.start() + result = self.dotflow.result_task() - self.assertEqual(len(result), 1) - self.assertIsInstance(result[0], Task) - self.assertEqual(result[0].status, TypeStatus.COMPLETED) + self.assertEqual(len(result), self.size) + self.assertIsInstance(result[self.index], Task) + self.assertEqual(result[self.index].status, StatusTaskType.COMPLETED) def test_result_context_with_start(self): - self.workflow.start() - result = self.workflow.result_context() + self.dotflow.start() + result = self.dotflow.result_context() - self.assertEqual(len(result), 1) - self.assertIsInstance(result[0], Context) + self.assertEqual(len(result), self.size) + self.assertIsInstance(result[self.index], Context) def test_result_storage_with_start(self): - self.workflow.start() - result = self.workflow.result_storage() + self.dotflow.start() + result = self.dotflow.result_storage() - self.assertEqual(len(result), 1) - self.assertEqual(result[0], {"foo": "bar"}) + self.assertEqual(len(result), self.size) + self.assertEqual(result[self.index], {"foo": "bar"}) def test_result_task_without_start(self): - result = self.workflow.result_task() + result = self.dotflow.result_task() - self.assertEqual(len(result), 1) - self.assertIsInstance(result[0], Task) - self.assertEqual(result[0].status, TypeStatus.NOT_STARTED) + self.assertEqual(len(result), self.size) + self.assertIsInstance(result[self.index], Task) + self.assertEqual(result[self.index].status, StatusTaskType.NOT_STARTED) def test_result_context_without_start(self): - result = self.workflow.result_context() + result = self.dotflow.result_context() - self.assertEqual(len(result), 1) - self.assertIsInstance(result[0], Context) + self.assertEqual(len(result), self.size) + self.assertIsInstance(result[self.index], Context) def test_result_storage_without_start(self): - result = self.workflow.result_storage() + result = self.dotflow.result_storage() + + self.assertEqual(len(result), self.size) + self.assertEqual(result[self.index], None) + + def test_dotflow_config_object(self): + self.assertIsNotNone(self.dotflow.config) + self.assertFalse(callable(self.dotflow.config)) + + def test_dotflow_workflow_id_object(self): + self.assertIsNotNone(self.dotflow.workflow_id) + self.assertFalse(callable(self.dotflow.workflow_id)) + + def test_dotflow_task_object(self): + self.assertIsNotNone(self.dotflow.task) + self.assertFalse(callable(self.dotflow.task)) + + def test_dotflow_add_object(self): + self.assertIsNotNone(self.dotflow.add) + self.assertTrue(callable(self.dotflow.add)) - self.assertEqual(len(result), 1) - self.assertEqual(result[0], None) + def test_dotflow_start_object(self): + self.assertIsNotNone(self.dotflow.start) + self.assertTrue(callable(self.dotflow.start)) diff --git a/tests/core/test_execution.py b/tests/core/test_execution.py index 39da73e..68e550d 100644 --- a/tests/core/test_execution.py +++ b/tests/core/test_execution.py @@ -9,7 +9,7 @@ from dotflow.core.context import Context from dotflow.core.execution import Execution -from dotflow.core.types import TypeStatus +from dotflow.core.types import StatusTaskType from dotflow.core.task import Task from tests.mocks import ( @@ -49,7 +49,7 @@ def test_execution_with_function_completed(self): task=task, workflow_id=workflow_id, previous_context=Context() ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.workflow_id, workflow_id) def test_execution_with_function_failed(self): @@ -59,7 +59,7 @@ def test_execution_with_function_failed(self): task=task, workflow_id=workflow_id, previous_context=Context() ) - self.assertEqual(controller.task.status, TypeStatus.FAILED) + self.assertEqual(controller.task.status, StatusTaskType.FAILED) self.assertEqual(controller.task.workflow_id, workflow_id) def test_execution_with_class_completed(self): @@ -78,7 +78,7 @@ def test_execution_with_class_completed(self): execution_log = log.message self.assertEqual(execution_log, "ActionStep: Run function executed") - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.workflow_id, workflow_id) def test_execution_with_class_failed(self): @@ -97,7 +97,7 @@ def test_execution_with_class_failed(self): execution_log = log.message self.assertEqual(execution_log, "ActionStepWithError: Run function executed") - self.assertEqual(controller.task.status, TypeStatus.FAILED) + self.assertEqual(controller.task.status, StatusTaskType.FAILED) self.assertEqual(controller.task.workflow_id, workflow_id) def test_execution_function_with_initial_context(self): @@ -111,7 +111,7 @@ def test_execution_function_with_initial_context(self): task=task, workflow_id=self.workflow_id, previous_context=None ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.initial_context.storage, self.context) def test_execution_function_with_previous_context(self): @@ -122,7 +122,7 @@ def test_execution_function_with_previous_context(self): task=task, workflow_id=self.workflow_id, previous_context=self.context ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task._previous_context.storage, self.context) def test_execution_function_with_contexts(self): @@ -136,7 +136,7 @@ def test_execution_function_with_contexts(self): task=task, workflow_id=self.workflow_id, previous_context=self.context ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.initial_context.storage, self.context) self.assertEqual(controller.task.previous_context.storage, self.context) @@ -151,7 +151,7 @@ def test_execution_class_with_initial_context(self): task=task, workflow_id=self.workflow_id, previous_context=None ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.initial_context.storage, self.context) def test_execution_class_with_previous_context(self): @@ -162,7 +162,7 @@ def test_execution_class_with_previous_context(self): task=task, workflow_id=self.workflow_id, previous_context=self.context ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.previous_context.storage, self.context) self.assertEqual( @@ -183,7 +183,7 @@ def test_execution_class_with_contexts(self): task=task, workflow_id=self.workflow_id, previous_context=self.context ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.initial_context.storage, self.context) self.assertEqual(controller.task.previous_context.storage, self.context) @@ -289,5 +289,5 @@ def test_valid_objects(self): previous_context=None ) - self.assertEqual(controller.task.status, TypeStatus.COMPLETED) + self.assertEqual(controller.task.status, StatusTaskType.COMPLETED) self.assertEqual(controller.task.current_context.storage, input_value) diff --git a/tests/core/test_queue.py b/tests/core/test_queue.py new file mode 100644 index 0000000..68f6da5 --- /dev/null +++ b/tests/core/test_queue.py @@ -0,0 +1,126 @@ +"""Test queue of task""" + +import unittest + +from dotflow.core.task import Task, Queue, QueueGroup, TASK_GROUP_NAME + +from tests.mocks import action_step, simple_callback + + +class TestQueue(unittest.TestCase): + + def setUp(self): + self.task = Task( + task_id=0, + step=action_step, + callback=simple_callback + ) + self.index = 0 + + def test_instantiating_queue_class(self): + expected_tasks = [] + queue = Queue() + self.assertIsInstance(queue, Queue) + self.assertListEqual(queue.tasks, expected_tasks) + + def test_queue_add(self): + queue = Queue() + queue.add(item=self.task) + + self.assertEqual(queue.tasks[self.index], self.task) + + def test_queue_remove(self): + queue = Queue() + queue.add(item=self.task) + queue.remove() + + self.assertFalse(queue.tasks) + + def test_queue_size(self): + expected_size = 1 + queue = Queue() + queue.add(item=self.task) + + self.assertEqual(queue.size(), expected_size) + + def test_queue_reverse(self): + expected_task = Task( + task_id=1, + step=action_step, + callback=simple_callback + ) + queue = Queue() + queue.add(item=self.task) + queue.add(item=expected_task) + queue.reverse() + + self.assertEqual(queue.tasks[self.index], expected_task) + + def test_queue_clear(self): + queue = Queue() + queue.add(item=self.task) + queue.clear() + + self.assertFalse(queue.tasks) + + def test_queue_get(self): + queue = Queue() + queue.add(item=self.task) + + self.assertListEqual(queue.get(), [self.task]) + + +class TestQueueGroup(unittest.TestCase): + + def setUp(self): + self.task = Task( + task_id=0, + step=action_step, + callback=simple_callback + ) + self.index = 0 + + def test_instantiating_queue_group_class(self): + expected_queue = {} + queue_group = QueueGroup() + + self.assertIsInstance(queue_group, QueueGroup) + self.assertDictEqual(queue_group.queue, expected_queue) + + def test_queue_group_add(self): + queue_group = QueueGroup() + + queue_group.add(item=self.task) + + self.assertIsNotNone(queue_group.queue.get(TASK_GROUP_NAME)) + self.assertIsInstance(queue_group.queue.get(TASK_GROUP_NAME), Queue) + self.assertEqual(queue_group.queue[TASK_GROUP_NAME].tasks[self.index], self.task) + + def test_queue_group_size(self): + expected_size = 1 + queue = QueueGroup() + + queue.add(item=self.task) + + self.assertEqual(queue.size(), expected_size) + + def test_queue_group_count(self): + expected_count = 2 + task = Task( + task_id=0, + step=action_step, + callback=simple_callback, + group_name="new_group" + ) + queue = QueueGroup() + queue.add(item=self.task) + queue.add(item=task) + + self.assertEqual(queue.count(), expected_count) + + def test_queue_group_tasks(self): + queue = QueueGroup() + queue.add(item=self.task) + + self.assertIsNotNone(queue.tasks()) + self.assertListEqual(queue.tasks(), [self.task]) diff --git a/tests/core/test_task.py b/tests/core/test_task.py index b8ddaed..0c0865d 100644 --- a/tests/core/test_task.py +++ b/tests/core/test_task.py @@ -8,7 +8,7 @@ from dotflow.core.action import Action from dotflow.core.config import Config from dotflow.core.context import Context -from dotflow.core.types.status import TypeStatus +from dotflow.core.types.status import StatusTaskType from dotflow.core.serializers.task import SerializerTaskError, SerializerTask from dotflow.core.exception import ( MissingActionDecorator, @@ -92,7 +92,7 @@ def test_task_schema(self): self.assertEqual(schema.task_id, 0) self.assertEqual(schema.workflow_id, expected_workflow_id) - self.assertEqual(schema.status, TypeStatus.NOT_STARTED) + self.assertEqual(schema.status, StatusTaskType.NOT_STARTED) self.assertEqual(schema.error.message, expected_error_message) self.assertEqual(schema.duration, expected_duration) self.assertEqual(schema.initial_context, json.dumps(self.content)) @@ -221,7 +221,7 @@ def test_set_error(self): self.assertIsInstance(self.task.error.exception, Exception) def test_set_status(self): - expected_value = TypeStatus.COMPLETED + expected_value = StatusTaskType.COMPLETED self.task.status = expected_value self.assertEqual(self.task.status, expected_value) diff --git a/tests/core/test_task_build.py b/tests/core/test_task_build.py index d616072..0a6446b 100644 --- a/tests/core/test_task_build.py +++ b/tests/core/test_task_build.py @@ -6,7 +6,7 @@ from dotflow.core.config import Config from dotflow.core.context import Context from dotflow.core.exception import MissingActionDecorator -from dotflow.core.task import Task, TaskBuilder +from dotflow.core.task import Task, TaskBuilder, QueueGroup, TASK_GROUP_NAME from dotflow.core.serializers.workflow import SerializerWorkflow from dotflow.core.serializers.task import SerializerTask from dotflow.utils import basic_callback @@ -23,32 +23,36 @@ def setUp(self): def test_instantiating_task_build_class(self): task = TaskBuilder(config=self.config) - self.assertListEqual(task.queue, []) + self.assertIsInstance(task.group, QueueGroup) def test_add_method(self): task = TaskBuilder(config=self.config) task.add(step=action_step) + tasks = task.group.tasks() - self.assertEqual(task.queue[0].task_id, 0) - self.assertIsInstance(task.queue[0], Task) - self.assertEqual(task.queue[0].callback, basic_callback) - self.assertEqual(len(task.queue), 1) + self.assertTrue(task.group.size()) + self.assertEqual(tasks[0].task_id, 0) + self.assertIsInstance(tasks[0], Task) + self.assertEqual(tasks[0].callback, basic_callback) + self.assertEqual(len(tasks), 1) def test_add_method_with_class_context(self): task = TaskBuilder(config=self.config) task.add(step=action_step, initial_context=Context(storage=self.content)) + tasks = task.group.tasks() - self.assertEqual(task.queue[0].initial_context.storage, self.content) + self.assertEqual(tasks[0].initial_context.storage, self.content) - self.assertIsInstance(task.queue[0].initial_context, Context) + self.assertIsInstance(tasks[0].initial_context, Context) def test_add_method_without_class_context(self): task = TaskBuilder(config=self.config) task.add(step=action_step, initial_context=self.content) + tasks = task.group.tasks() - self.assertEqual(task.queue[0].initial_context.storage, self.content) + self.assertEqual(tasks[0].initial_context.storage, self.content) - self.assertIsInstance(task.queue[0].initial_context, Context) + self.assertIsInstance(tasks[0].initial_context, Context) def test_count_method(self): task = TaskBuilder(config=self.config) @@ -56,11 +60,11 @@ def test_count_method(self): initial_count = 0 final_count = 1 - self.assertEqual(task.count(), initial_count) + self.assertEqual(task.group.size(), initial_count) task.add(step=action_step) - self.assertEqual(task.count(), final_count) + self.assertEqual(task.group.size(), final_count) def test_clear_method(self): task = TaskBuilder(config=self.config) @@ -69,11 +73,11 @@ def test_clear_method(self): expected_count_after = 0 task.add(step=action_step) - self.assertEqual(task.count(), expected_count_before) + self.assertEqual(task.group.size(), expected_count_before) - task.clear() + task.group.queue[TASK_GROUP_NAME].clear() - self.assertEqual(task.count(), expected_count_after) + self.assertEqual(task.group.size(), expected_count_after) def test_with_method_step_without_decorator(self): task = TaskBuilder(config=self.config) diff --git a/tests/core/test_workflow.py b/tests/core/test_workflow.py index b2b56df..80ba42b 100644 --- a/tests/core/test_workflow.py +++ b/tests/core/test_workflow.py @@ -7,9 +7,9 @@ from types import FunctionType from dotflow.core.workflow import Manager -from dotflow.core.types import TypeExecution, TypeStatus +from dotflow.core.types import ExecutionModeType, StatusTaskType from dotflow.core.exception import ExecutionModeNotExist -from dotflow.core.task import Task +from dotflow.core.task import Task, QueueGroup from tests.mocks import ( ActionStep, @@ -22,82 +22,99 @@ class TestWorkflow(unittest.TestCase): def setUp(self): - task = Task( - task_id=0, - step=action_step, - callback=simple_callback + self.group = QueueGroup() + self.group.add( + item=Task( + task_id=0, + step=action_step, + callback=simple_callback + ) ) - self.tasks = [task] def test_instantiating_workflow_class(self): - controller = Manager(tasks=self.tasks) + controller = Manager(group=self.group) - self.assertListEqual(controller.tasks, self.tasks) + self.assertEqual(controller.group, self.group) self.assertIsInstance(controller.workflow_id, UUID) self.assertIsInstance(controller.on_success, FunctionType) self.assertIsInstance(controller.on_failure, FunctionType) def test_workflow_with_function_completed(self): - task = Task( - task_id=0, - step=action_step, - callback=simple_callback + group = QueueGroup() + group.add( + item=Task( + task_id=0, + step=action_step, + callback=simple_callback + ) ) - controller = Manager(tasks=[task]) - self.assertEqual(controller.tasks[0].status, TypeStatus.COMPLETED) + controller = Manager(group=group) + self.assertEqual(controller.group.tasks()[0].status, StatusTaskType.COMPLETED) def test_workflow_with_function_failed(self): - task = Task( - task_id=0, - step=action_step_with_error, - callback=simple_callback + group = QueueGroup() + group.add( + item=Task( + task_id=0, + step=action_step_with_error, + callback=simple_callback + ) ) - controller = Manager(tasks=[task]) - self.assertEqual(controller.tasks[0].status, TypeStatus.FAILED) + controller = Manager(group=group) + self.assertEqual(controller.group.tasks()[0].status, StatusTaskType.FAILED) def test_with_execution_mode_that_does_not_exist(self): with self.assertRaises(ExecutionModeNotExist): - Manager(tasks=self.tasks, mode="unknown") + Manager(group=self.group, mode="unknown") def test_with_execution_mode_sequential(self): - Manager(tasks=self.tasks, mode=TypeExecution.SEQUENTIAL) + Manager(group=self.group, mode=ExecutionModeType.SEQUENTIAL) def test_with_execution_mode_background(self): - Manager(tasks=self.tasks, mode=TypeExecution.BACKGROUND) + Manager(group=self.group, mode=ExecutionModeType.BACKGROUND) def test_with_execution_mode_parallel(self): - Manager(tasks=self.tasks, mode=TypeExecution.PARALLEL) + Manager(group=self.group, mode=ExecutionModeType.PARALLEL) def test_callback_success_called(self): - task = Task( - task_id=0, - step=action_step, - callback=simple_callback + group = QueueGroup() + group.add( + item=Task( + task_id=0, + step=action_step, + callback=simple_callback + ) ) mock_success = Mock() - Manager(tasks=[task], on_success=mock_success) + Manager(group=group, on_success=mock_success) mock_success.assert_called() def test_callback_failure_called(self): - task = Task( - task_id=0, - step=action_step_with_error, - callback=simple_callback + group = QueueGroup() + group.add( + item=Task( + task_id=0, + step=action_step_with_error, + callback=simple_callback + ) ) mock_failure = Mock() - Manager(tasks=[task], on_failure=mock_failure) + Manager(group=group, on_failure=mock_failure) mock_failure.assert_called() def test_workflow_with_class_completed(self): - task = Task( - task_id=0, - step=ActionStep, - callback=simple_callback + group = QueueGroup() + group.add( + item=Task( + task_id=0, + step=ActionStep, + callback=simple_callback + ) ) - controller = Manager(tasks=[task]) - self.assertEqual(controller.tasks[0].status, TypeStatus.COMPLETED) + controller = Manager(group=group) + self.assertEqual(controller.group.tasks()[0].status, StatusTaskType.COMPLETED) diff --git a/tests/core/test_workflow_background.py b/tests/core/test_workflow_background.py index 8b01d5b..46962fd 100644 --- a/tests/core/test_workflow_background.py +++ b/tests/core/test_workflow_background.py @@ -4,9 +4,9 @@ from uuid import uuid4 -from dotflow.core.workflow import Background, grouper -from dotflow.core.types import TypeStatus -from dotflow.core.task import Task, TaskError +from dotflow.core.workflow import Background +from dotflow.core.types import StatusTaskType +from dotflow.core.task import Task, TaskError, QueueGroup from tests.mocks import ( action_step, @@ -22,66 +22,72 @@ def setUp(self): self.ignore = False def test_instantiating_background_class(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Background( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertListEqual(tasks, tasks) - self.assertDictEqual(execution.groups, groups) + self.assertIsInstance(execution.group, QueueGroup) self.assertEqual(execution.workflow_id, self.workflow_id) self.assertEqual(execution.ignore, self.ignore) def test_workflow_with_background_function_completed(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Background( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() - self.assertEqual(tasks[0].status, TypeStatus.COMPLETED) + self.assertEqual(tasks[0].status, StatusTaskType.COMPLETED) self.assertEqual(tasks[0].current_context.storage, {"foo": "bar"}) self.assertIsInstance(tasks[0].error, TaskError) self.assertEqual(tasks[0].error.message, "") def test_workflow_with_background_function_failed(self): - tasks = [Task(task_id=0, step=action_step_with_error, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step_with_error, callback=simple_callback) + ) execution = Background( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() - self.assertEqual(tasks[0].status, TypeStatus.FAILED) + self.assertEqual(tasks[0].status, StatusTaskType.FAILED) self.assertIsNone(tasks[0].current_context.storage) self.assertIsInstance(tasks[0].error, TaskError) self.assertEqual(tasks[0].error.message, "Fail!") def test_instantiating_background_setup_queue(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Background( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() @@ -90,17 +96,17 @@ def test_instantiating_background_setup_queue(self): def test_instantiating_background_flow_callback(self): task = Task(task_id=5, step=action_step, callback=simple_callback) - groups = grouper(tasks=[task]) + group = QueueGroup() + group.add(item=task) execution = Background( - tasks=[task], workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() execution._flow_callback(task=task) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertEqual(tasks[0].task_id, 5) diff --git a/tests/core/test_workflow_parallel.py b/tests/core/test_workflow_parallel.py index 48c0341..d523ada 100644 --- a/tests/core/test_workflow_parallel.py +++ b/tests/core/test_workflow_parallel.py @@ -5,9 +5,9 @@ from uuid import uuid4 from multiprocessing.queues import Queue -from dotflow.core.workflow import Parallel, grouper -from dotflow.core.types import TypeStatus -from dotflow.core.task import Task, TaskError +from dotflow.core.workflow import Parallel +from dotflow.core.types import StatusTaskType +from dotflow.core.task import Task, TaskError, QueueGroup from tests.mocks import ( action_step, @@ -23,66 +23,72 @@ def setUp(self): self.ignore = False def test_instantiating_parallel_class(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Parallel( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertListEqual(tasks, tasks) - self.assertDictEqual(execution.groups, groups) + self.assertIsInstance(execution.group, QueueGroup) self.assertEqual(execution.workflow_id, self.workflow_id) self.assertEqual(execution.ignore, self.ignore) def test_workflow_with_parallel_function_completed(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Parallel( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() - self.assertEqual(tasks[0].status, TypeStatus.COMPLETED) + self.assertEqual(tasks[0].status, StatusTaskType.COMPLETED) self.assertEqual(tasks[0].current_context.storage, {"foo": "bar"}) self.assertIsInstance(tasks[0].error, TaskError) self.assertEqual(tasks[0].error.message, "") def test_workflow_with_parallel_function_failed(self): - tasks = [Task(task_id=0, step=action_step_with_error, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step_with_error, callback=simple_callback) + ) execution = Parallel( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() - self.assertEqual(tasks[0].status, TypeStatus.FAILED) + self.assertEqual(tasks[0].status, StatusTaskType.FAILED) self.assertIsNone(tasks[0].current_context.storage) self.assertIsInstance(tasks[0].error, TaskError) self.assertEqual(tasks[0].error.message, "Fail!") def test_instantiating_parallel_setup_queue(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Parallel( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() @@ -91,17 +97,19 @@ def test_instantiating_parallel_setup_queue(self): def test_instantiating_parallel_flow_callback(self): task = Task(task_id=5, step=action_step, callback=simple_callback) - groups = grouper(tasks=[task]) + group = QueueGroup() + group.add( + item=Task(task_id=5, step=action_step, callback=simple_callback) + ) execution = Parallel( - tasks=[task], workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() execution._flow_callback(task=task) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertEqual(tasks[0].task_id, 5) diff --git a/tests/core/test_workflow_sequential.py b/tests/core/test_workflow_sequential.py index beddac0..3021980 100644 --- a/tests/core/test_workflow_sequential.py +++ b/tests/core/test_workflow_sequential.py @@ -5,9 +5,9 @@ from uuid import uuid4 from unittest.mock import Mock -from dotflow.core.workflow import Sequential, grouper -from dotflow.core.types import TypeStatus -from dotflow.core.task import Task, TaskError +from dotflow.core.workflow import Sequential +from dotflow.core.types import StatusTaskType +from dotflow.core.task import Task, TaskError, QueueGroup from tests.mocks import ( action_step, @@ -23,73 +23,82 @@ def setUp(self): self.ignore = False def test_instantiating_sequential_class(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Sequential( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) - self.assertListEqual(execution.tasks, tasks) - self.assertDictEqual(execution.groups, groups) + self.assertIsInstance(execution.group, QueueGroup) self.assertEqual(execution.workflow_id, self.workflow_id) self.assertEqual(execution.ignore, self.ignore) def test_workflow_with_callback(self): mock_callback = Mock() - tasks = [Task(task_id=0, step=action_step, callback=mock_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=mock_callback) + ) Sequential( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) mock_callback.assert_called() def test_workflow_with_function_completed(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Sequential( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) + tasks = execution.group.tasks() - self.assertEqual(execution.tasks[0].status, TypeStatus.COMPLETED) - self.assertEqual(execution.tasks[0].current_context.storage, {"foo": "bar"}) - self.assertIsInstance(execution.tasks[0].error, TaskError) - self.assertEqual(execution.tasks[0].error.message, "") + self.assertEqual(tasks[0].status, StatusTaskType.COMPLETED) + self.assertEqual(tasks[0].current_context.storage, {"foo": "bar"}) + self.assertIsInstance(tasks[0].error, TaskError) + self.assertEqual(tasks[0].error.message, "") def test_workflow_with_function_failed(self): - tasks = [Task(task_id=0, step=action_step_with_error, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step_with_error, callback=simple_callback) + ) execution = Sequential( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) + tasks = execution.group.tasks() - self.assertEqual(execution.tasks[0].status, TypeStatus.FAILED) - self.assertIsNone(execution.tasks[0].current_context.storage) - self.assertIsInstance(execution.tasks[0].error, TaskError) - self.assertEqual(execution.tasks[0].error.message, "Fail!") + self.assertEqual(tasks[0].status, StatusTaskType.FAILED) + self.assertIsNone(tasks[0].current_context.storage) + self.assertIsInstance(tasks[0].error, TaskError) + self.assertEqual(tasks[0].error.message, "Fail!") def test_instantiating_sequential_setup_queue(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = Sequential( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() @@ -98,17 +107,17 @@ def test_instantiating_sequential_setup_queue(self): def test_instantiating_sequential_flow_callback(self): task = Task(task_id=5, step=action_step, callback=simple_callback) - groups = grouper(tasks=[task]) + group = QueueGroup() + group.add(item=task) execution = Sequential( - tasks=[task], workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() execution._flow_callback(task=task) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertEqual(tasks[0].task_id, 5) diff --git a/tests/core/test_workflow_sequential_group.py b/tests/core/test_workflow_sequential_group.py index 6348d90..d1987f3 100644 --- a/tests/core/test_workflow_sequential_group.py +++ b/tests/core/test_workflow_sequential_group.py @@ -6,9 +6,9 @@ from multiprocessing.queues import Queue -from dotflow.core.workflow import SequentialGroup, grouper -from dotflow.core.types import TypeStatus -from dotflow.core.task import Task, TaskError +from dotflow.core.workflow import SequentialGroup +from dotflow.core.types import StatusTaskType +from dotflow.core.task import Task, TaskError, QueueGroup from tests.mocks import ( action_step, @@ -24,66 +24,72 @@ def setUp(self): self.ignore = False def test_instantiating_sequential_group_class(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = SequentialGroup( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertListEqual(tasks, tasks) - self.assertDictEqual(execution.groups, groups) + self.assertIsInstance(execution.group, QueueGroup) self.assertEqual(execution.workflow_id, self.workflow_id) self.assertEqual(execution.ignore, self.ignore) def test_workflow_with_sequential_group_function_completed(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = SequentialGroup( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() - self.assertEqual(tasks[0].status, TypeStatus.COMPLETED) + self.assertEqual(tasks[0].status, StatusTaskType.COMPLETED) self.assertEqual(tasks[0].current_context.storage, {"foo": "bar"}) self.assertIsInstance(tasks[0].error, TaskError) self.assertEqual(tasks[0].error.message, "") def test_workflow_with_sequential_group_function_failed(self): - tasks = [Task(task_id=0, step=action_step_with_error, callback=simple_callback)] + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step_with_error, callback=simple_callback) + ) execution = SequentialGroup( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=grouper(tasks=tasks), + group=group, ) - tasks = execution.get_tasks() + tasks = execution.transport() - self.assertEqual(tasks[0].status, TypeStatus.FAILED) + self.assertEqual(tasks[0].status, StatusTaskType.FAILED) self.assertIsNone(tasks[0].current_context.storage) self.assertIsInstance(tasks[0].error, TaskError) self.assertEqual(tasks[0].error.message, "Fail!") def test_instantiating_sequential_group_setup_queue(self): - tasks = [Task(task_id=0, step=action_step, callback=simple_callback)] - groups = grouper(tasks=tasks) + group = QueueGroup() + group.add( + item=Task(task_id=0, step=action_step, callback=simple_callback) + ) execution = SequentialGroup( - tasks=tasks, workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() @@ -92,17 +98,17 @@ def test_instantiating_sequential_group_setup_queue(self): def test_instantiating_sequential_group_flow_callback(self): task = Task(task_id=5, step=action_step, callback=simple_callback) - groups = grouper(tasks=[task]) + group = QueueGroup() + group.add(item=task) execution = SequentialGroup( - tasks=[task], workflow_id=self.workflow_id, ignore=self.ignore, - groups=groups, + group=group, ) execution.setup_queue() execution._flow_callback(task=task) - tasks = execution.get_tasks() + tasks = execution.transport() self.assertEqual(tasks[0].task_id, 5) diff --git a/tests/test_integration.py b/tests/integration.py similarity index 100% rename from tests/test_integration.py rename to tests/integration.py diff --git a/tests/test_flow.py b/tests/test_flow.py index a60a094..a0528e9 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -95,19 +95,19 @@ def load_task_y(previous_context): def main(): workflow = DotFlow() - workflow.task.add(step=StepX) - workflow.task.add(step=StepY, initial_context={"foo": "bar"}) - workflow.task.add(step=simple_step) - workflow.task.add(step=extract_task_x) - workflow.task.add(step=transform_task_x, initial_context={"foo": True}) - workflow.task.add(step=load_task_x) - workflow.task.add(step=extract_task_y) - workflow.task.add(step=transform_task_y) - workflow.task.add(step=load_task_y) + workflow.add(step=StepX) + workflow.add(step=StepY, initial_context={"foo": "bar"}) + workflow.add(step=simple_step) + workflow.add(step=extract_task_x) + workflow.add(step=transform_task_x, initial_context={"foo": True}) + workflow.add(step=load_task_x) + workflow.add(step=extract_task_y) + workflow.add(step=transform_task_y) + workflow.add(step=load_task_y) workflow.start() workflow = DotFlow() - workflow.task.add( + workflow.add( [ StepX, StepY, @@ -120,7 +120,7 @@ def main(): workflow.start() workflow = DotFlow() - workflow.task.add( + workflow.add( [ "tests.test_flow.StepX", "tests.test_flow.StepY",