aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-11-11 01:12:29 +0800
committerYuqian Yang <crupest@crupest.life>2024-12-18 18:31:27 +0800
commitaaa855e3839130a79193f38969f07763f2773c5d (patch)
treee4cb238df4588f4633d9c1190136895865d51a98
parent95da3ade5bfa6ef39923cd3fc2a551ad983c1537 (diff)
downloadcrupest-aaa855e3839130a79193f38969f07763f2773c5d.tar.gz
crupest-aaa855e3839130a79193f38969f07763f2773c5d.tar.bz2
crupest-aaa855e3839130a79193f38969f07763f2773c5d.zip
HALF WORK: 2024.11.27
-rw-r--r--.editorconfig2
-rw-r--r--tools/cru-py/.flake84
-rw-r--r--tools/cru-py/__init__.py0
-rw-r--r--tools/cru-py/cru/__init__.py1
-rw-r--r--tools/cru-py/cru/_util/__init__.py63
-rw-r--r--tools/cru-py/cru/_util/_const.py49
-rw-r--r--tools/cru-py/cru/_util/_cru.py (renamed from tools/cru-py/cru/util/_cru.py)22
-rw-r--r--tools/cru-py/cru/_util/_event.py (renamed from tools/cru-py/cru/util/_event.py)0
-rw-r--r--tools/cru-py/cru/_util/_func.py259
-rw-r--r--tools/cru-py/cru/_util/_lang.py16
-rw-r--r--tools/cru-py/cru/_util/_list.py (renamed from tools/cru-py/cru/util/_list.py)511
-rw-r--r--tools/cru-py/cru/_util/_type.py (renamed from tools/cru-py/cru/util/_type.py)0
-rw-r--r--tools/cru-py/cru/attr.py2
-rw-r--r--tools/cru-py/cru/excp.py2
-rw-r--r--tools/cru-py/cru/service/docker.py2
-rw-r--r--tools/cru-py/cru/util/__init__.py25
-rw-r--r--tools/cru-py/cru/util/_const.py56
-rw-r--r--tools/cru-py/cru/util/_func.py126
-rw-r--r--tools/cru-py/crupest/aio.py (renamed from tools/cru-py/aio.py)24
-rw-r--r--tools/cru-py/poetry.lock642
-rw-r--r--tools/cru-py/pyproject.toml28
-rw-r--r--tools/cru-py/requirements.txt3
22 files changed, 1419 insertions, 418 deletions
diff --git a/.editorconfig b/.editorconfig
index edb912e..d55cc4a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,3 +3,5 @@ root = true
[*]
end_of_line = lf
+[*.py]
+profile = black
diff --git a/tools/cru-py/.flake8 b/tools/cru-py/.flake8
new file mode 100644
index 0000000..a77cb08
--- /dev/null
+++ b/tools/cru-py/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length = 80
+extend-select = B950
+extend-ignore = E203,E501,E701
diff --git a/tools/cru-py/__init__.py b/tools/cru-py/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tools/cru-py/__init__.py
+++ /dev/null
diff --git a/tools/cru-py/cru/__init__.py b/tools/cru-py/cru/__init__.py
index e36a778..2ae241e 100644
--- a/tools/cru-py/cru/__init__.py
+++ b/tools/cru-py/cru/__init__.py
@@ -4,6 +4,7 @@ import sys
class CruInitError(Exception):
pass
+
def check_python_version(required_version=(3, 11)):
if sys.version_info < required_version:
raise CruInitError(f"Python version must be >= {required_version}!")
diff --git a/tools/cru-py/cru/_util/__init__.py b/tools/cru-py/cru/_util/__init__.py
new file mode 100644
index 0000000..481502c
--- /dev/null
+++ b/tools/cru-py/cru/_util/__init__.py
@@ -0,0 +1,63 @@
+from ._const import (
+ CruNotFound,
+ CruUseDefault,
+ CruDontChange,
+ CruNoValue,
+ CruPlaceholder,
+)
+from ._func import (
+ CruFunction,
+ CruFunctionMeta,
+ CruRawFunctions,
+ CruWrappedFunctions,
+ CruFunctionGenerators,
+)
+from ._list import (
+ CruList,
+ CruInplaceList,
+ CruUniqueKeyInplaceList,
+ ListOperations,
+ CanBeList,
+ ElementOperation,
+ ElementPredicate,
+ ElementTransformer,
+ OptionalElementOperation,
+ ElementPredicate,
+ OptionalElementTransformer,
+)
+from ._type import TypeSet
+
+F = CruFunction
+WF = CruWrappedFunctions
+FG = CruFunctionGenerators
+L = CruList
+
+
+__all__ = [
+ "CruNotFound",
+ "CruUseDefault",
+ "CruDontChange",
+ "CruNoValue",
+ "CruPlaceholder",
+ "CruFunction",
+ "CruFunctionMeta",
+ "CruRawFunctions",
+ "CruWrappedFunctions",
+ "CruFunctionGenerators",
+ "CruList",
+ "CruInplaceList",
+ "CruUniqueKeyInplaceList",
+ "ListOperations",
+ "CanBeList",
+ "ElementOperation",
+ "ElementPredicate",
+ "ElementTransformer",
+ "OptionalElementOperation",
+ "ElementPredicate",
+ "OptionalElementTransformer",
+ "TypeSet",
+ "F",
+ "WF",
+ "FG",
+ "L",
+]
diff --git a/tools/cru-py/cru/_util/_const.py b/tools/cru-py/cru/_util/_const.py
new file mode 100644
index 0000000..bc02c3a
--- /dev/null
+++ b/tools/cru-py/cru/_util/_const.py
@@ -0,0 +1,49 @@
+from enum import Enum, auto
+from typing import Self, TypeGuard, TypeVar
+
+from ._cru import CRU
+
+_T = TypeVar("_T")
+
+
+class CruConstantBase(Enum):
+ @classmethod
+ def check(cls, v: _T | Self) -> TypeGuard[Self]:
+ return isinstance(v, cls)
+
+ @classmethod
+ def check_not(cls, v: _T | Self) -> TypeGuard[_T]:
+ return not cls.check(v)
+
+ @classmethod
+ def value(cls) -> Self:
+ return cls.VALUE # type: ignore
+
+
+class CruNotFound(CruConstantBase):
+ VALUE = auto()
+
+
+class CruUseDefault(CruConstantBase):
+ VALUE = auto()
+
+
+class CruDontChange(CruConstantBase):
+ VALUE = auto()
+
+
+class CruNoValue(CruConstantBase):
+ VALUE = auto()
+
+
+class CruPlaceholder(CruConstantBase):
+ VALUE = auto()
+
+
+CRU.add_objects(
+ CruNotFound,
+ CruUseDefault,
+ CruDontChange,
+ CruNoValue,
+ CruPlaceholder,
+)
diff --git a/tools/cru-py/cru/util/_cru.py b/tools/cru-py/cru/_util/_cru.py
index 61a0ee1..0085a80 100644
--- a/tools/cru-py/cru/util/_cru.py
+++ b/tools/cru-py/cru/_util/_cru.py
@@ -1,10 +1,12 @@
from typing import Any
+from ._lang import remove_none
+
class _Cru:
NAME_PREFIXES = ("CRU_", "Cru", "cru_")
- def __init__(self):
+ def __init__(self) -> None:
self._d: dict[str, Any] = {}
def all_names(self) -> list[str]:
@@ -20,12 +22,13 @@ class _Cru:
def _maybe_remove_prefix(name: str) -> str | None:
for prefix in _Cru.NAME_PREFIXES:
if name.startswith(prefix):
- return name[len(prefix):]
+ return name[len(prefix) :]
return None
- def _check_name_exist(self, *names: str) -> None:
+ def _check_name_exist(self, *names: str | None) -> None:
for name in names:
- if name is None: continue
+ if name is None:
+ continue
if self.has_name(name):
raise ValueError(f"Name {name} exists in CRU.")
@@ -41,13 +44,13 @@ class _Cru:
return _Cru.check_name_format(o.__name__)
def _do_add(self, o, *names: str | None) -> list[str]:
- names = set(names)
- names.remove(None)
- for name in names:
+ name_list: list[str] = remove_none(names)
+ for name in name_list:
self._d[name] = o
- return list(names)
+ return name_list
def add(self, o, name: str | None) -> tuple[str, str | None]:
+ no_prefix_name: str | None
if name is None:
name, no_prefix_name = self._check_object_name(o)
else:
@@ -58,7 +61,8 @@ class _Cru:
return name, no_prefix_name
def add_with_alias(self, o, name: str | None = None, *aliases: str) -> list[str]:
- final_names = []
+ final_names: list[str | None] = []
+ no_prefix_name: str | None
if name is None:
name, no_prefix_name = self._check_object_name(o)
self._check_name_exist(name, no_prefix_name)
diff --git a/tools/cru-py/cru/util/_event.py b/tools/cru-py/cru/_util/_event.py
index 813e33f..813e33f 100644
--- a/tools/cru-py/cru/util/_event.py
+++ b/tools/cru-py/cru/_util/_event.py
diff --git a/tools/cru-py/cru/_util/_func.py b/tools/cru-py/cru/_util/_func.py
new file mode 100644
index 0000000..0b8b07a
--- /dev/null
+++ b/tools/cru-py/cru/_util/_func.py
@@ -0,0 +1,259 @@
+from __future__ import annotations
+
+from collections.abc import Callable
+from enum import Flag, auto
+from typing import (
+ Any,
+ Generic,
+ Iterable,
+ Literal,
+ ParamSpec,
+ TypeAlias,
+ TypeVar,
+ cast,
+)
+
+from ._cru import CRU
+from ._const import CruPlaceholder
+
+_P = ParamSpec("_P")
+_T = TypeVar("_T")
+
+
+class CruRawFunctions:
+ @staticmethod
+ def none(*_v, **_kwargs) -> None:
+ return None
+
+ @staticmethod
+ def true(*_v, **_kwargs) -> Literal[True]:
+ return True
+
+ @staticmethod
+ def false(*_v, **_kwargs) -> Literal[False]:
+ return False
+
+ @staticmethod
+ def identity(v: _T) -> _T:
+ return v
+
+ @staticmethod
+ def only_you(v: _T, *_v, **_kwargs) -> _T:
+ return v
+
+ @staticmethod
+ def equal(a: Any, b: Any) -> bool:
+ return a == b
+
+ @staticmethod
+ def not_equal(a: Any, b: Any) -> bool:
+ return a != b
+
+ @staticmethod
+ def not_(v: Any) -> Any:
+ return not v
+
+
+CruArgsChainableCallable: TypeAlias = Callable[..., Iterable[Any]]
+CruKwargsChainableCallable: TypeAlias = Callable[..., Iterable[tuple[str, Any]]]
+CruChainableCallable: TypeAlias = Callable[
+ ..., tuple[Iterable[Any], Iterable[tuple[str, Any]]]
+]
+
+
+class CruFunctionChainMode(Flag):
+ ARGS = auto()
+ KWARGS = auto()
+ BOTH = ARGS | KWARGS
+
+
+class CruFunctionMeta:
+ @staticmethod
+ def bind(func: Callable[..., _T], *bind_args, **bind_kwargs) -> Callable[..., _T]:
+ def bound_func(*args, **kwargs):
+ popped = 0
+ real_args = []
+ for arg in bind_args:
+ if CruPlaceholder.check(arg):
+ real_args.append(args[popped])
+ popped += 1
+ else:
+ real_args.append(arg)
+ real_args.extend(args[popped:])
+ return func(*real_args, **(bind_kwargs | kwargs))
+
+ return bound_func
+
+ @staticmethod
+ def chain_with_args(
+ funcs: Iterable[CruArgsChainableCallable], *bind_args, **bind_kwargs
+ ) -> CruArgsChainableCallable:
+ def chained_func(*args):
+ for func in funcs:
+ args = CruFunctionMeta.bind(func, *bind_args, **bind_kwargs)(*args)
+ return args
+
+ return chained_func
+
+ @staticmethod
+ def chain_with_kwargs(
+ funcs: Iterable[CruKwargsChainableCallable], *bind_args, **bind_kwargs
+ ) -> CruKwargsChainableCallable:
+ def chained_func(**kwargs):
+ for func in funcs:
+ kwargs = CruFunctionMeta.bind(func, *bind_args, **bind_kwargs)(**kwargs)
+ return kwargs
+
+ return chained_func
+
+ @staticmethod
+ def chain_with_both(
+ funcs: Iterable[CruChainableCallable], *bind_args, **bind_kwargs
+ ) -> CruChainableCallable:
+ def chained_func(*args, **kwargs):
+ for func in funcs:
+ args, kwargs = CruFunctionMeta.bind(func, *bind_args, **bind_kwargs)(
+ *args, **kwargs
+ )
+ return args, kwargs
+
+ return chained_func
+
+ @staticmethod
+ def chain(
+ mode: CruFunctionChainMode,
+ funcs: Iterable[
+ CruArgsChainableCallable | CruKwargsChainableCallable | CruChainableCallable
+ ],
+ *bind_args,
+ **bind_kwargs,
+ ) -> CruArgsChainableCallable | CruKwargsChainableCallable | CruChainableCallable:
+ if mode == CruFunctionChainMode.ARGS:
+ return CruFunctionMeta.chain_with_args(
+ cast(Iterable[CruArgsChainableCallable], funcs),
+ *bind_args,
+ **bind_kwargs,
+ )
+ elif mode == CruFunctionChainMode.KWARGS:
+ return CruFunctionMeta.chain_with_kwargs(
+ cast(Iterable[CruKwargsChainableCallable], funcs),
+ *bind_args,
+ **bind_kwargs,
+ )
+ elif mode == CruFunctionChainMode.BOTH:
+ return CruFunctionMeta.chain_with_both(
+ cast(Iterable[CruChainableCallable], funcs), *bind_args, **bind_kwargs
+ )
+
+
+# Advanced Function Wrapper
+class CruFunction(Generic[_P, _T]):
+
+ def __init__(self, f: Callable[_P, _T]):
+ self._f = f
+
+ @property
+ def f(self) -> Callable[_P, _T]:
+ return self._f
+
+ @property
+ def func(self) -> Callable[_P, _T]:
+ return self.f
+
+ def bind(self, *bind_args, **bind_kwargs) -> CruFunction[..., _T]:
+ self._f = CruFunctionMeta.bind(self._f, *bind_args, **bind_kwargs)
+ return self
+
+ def _iter_with_self(
+ self, funcs: Iterable[Callable[..., Any]]
+ ) -> Iterable[Callable[..., Any]]:
+ yield self
+ yield from funcs
+
+ @staticmethod
+ def chain_with_args(
+ self,
+ funcs: Iterable[CruArgsChainableCallable],
+ *bind_args,
+ **bind_kwargs,
+ ) -> CruArgsChainableCallable:
+ return CruFunction(
+ CruFunctionMeta.chain_with_args(
+ self._iter_with_self(funcs), *bind_args, **bind_kwargs
+ )
+ )
+
+ def chain_with_kwargs(
+ self, funcs: Iterable[CruKwargsChainableCallable], *bind_args, **bind_kwargs
+ ) -> CruKwargsChainableCallable:
+ return CruFunction(
+ CruFunctionMeta.chain_with_kwargs(
+ self._iter_with_self(funcs), *bind_args, **bind_kwargs
+ )
+ )
+
+ def chain_with_both(
+ self, funcs: Iterable[CruChainableCallable], *bind_args, **bind_kwargs
+ ) -> CruChainableCallable:
+ return CruFunction(
+ CruFunctionMeta.chain_with_both(
+ self._iter_with_self(funcs), *bind_args, **bind_kwargs
+ )
+ )
+
+ def chain(
+ self,
+ mode: CruFunctionChainMode,
+ funcs: Iterable[
+ CruArgsChainableCallable | CruKwargsChainableCallable | CruChainableCallable
+ ],
+ *bind_args,
+ **bind_kwargs,
+ ) -> CruArgsChainableCallable | CruKwargsChainableCallable | CruChainableCallable:
+ return CruFunction(
+ CruFunctionMeta.chain(
+ mode, self._iter_with_self(funcs), *bind_args, **bind_kwargs
+ )
+ )
+
+ def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _T:
+ return self._f(*args, **kwargs)
+
+ @staticmethod
+ def make_chain(
+ mode: CruFunctionChainMode,
+ funcs: Iterable[
+ CruArgsChainableCallable | CruKwargsChainableCallable | CruChainableCallable
+ ],
+ *bind_args,
+ **bind_kwargs,
+ ) -> CruFunction:
+ return CruFunction(
+ CruFunctionMeta.chain(mode, funcs, *bind_args, **bind_kwargs)
+ )
+
+
+class CruWrappedFunctions:
+ none = CruFunction(CruRawFunctions.none)
+ true = CruFunction(CruRawFunctions.true)
+ false = CruFunction(CruRawFunctions.false)
+ identity = CruFunction(CruRawFunctions.identity)
+ only_you = CruFunction(CruRawFunctions.only_you)
+ equal = CruFunction(CruRawFunctions.equal)
+ not_equal = CruFunction(CruRawFunctions.not_equal)
+ not_ = CruFunction(CruRawFunctions.not_)
+
+
+class CruFunctionGenerators:
+ @staticmethod
+ def make_isinstance_of_types(*types: type) -> Callable:
+ return CruFunction(lambda v: type(v) in types)
+
+
+CRU.add_objects(
+ CruRawFunctions,
+ CruFunctionMeta,
+ CruFunction,
+ CruWrappedFunctions,
+ CruFunctionGenerators,
+)
diff --git a/tools/cru-py/cru/_util/_lang.py b/tools/cru-py/cru/_util/_lang.py
new file mode 100644
index 0000000..925ba00
--- /dev/null
+++ b/tools/cru-py/cru/_util/_lang.py
@@ -0,0 +1,16 @@
+from collections.abc import Callable
+from typing import Any, Iterable, TypeVar, cast
+
+T = TypeVar("T")
+D = TypeVar("D")
+
+
+def remove_element(
+ iterable: Iterable[T | None], to_rm: Iterable[Any], des: type[D] | None = None
+) -> D:
+ to_rm = set(to_rm)
+ return cast(Callable[..., D], des or list)(v for v in iterable if v not in to_rm)
+
+
+def remove_none(iterable: Iterable[T | None], des: type[D] | None = None) -> D:
+ return cast(Callable[..., D], des or list)(v for v in iterable if v is not None)
diff --git a/tools/cru-py/cru/util/_list.py b/tools/cru-py/cru/_util/_list.py
index e1c8373..711d5f1 100644
--- a/tools/cru-py/cru/util/_list.py
+++ b/tools/cru-py/cru/_util/_list.py
@@ -1,85 +1,235 @@
+from __future__ import annotations
+
from collections.abc import Iterable, Callable
from dataclasses import dataclass
from enum import Enum
-from typing import TypeVar, ParamSpec, Any, Generic, ClassVar, Optional, Union
-
-from ._const import CRU_NOT_FOUND
-
+from typing import (
+ Generator,
+ Literal,
+ Self,
+ TypeAlias,
+ TypeVar,
+ ParamSpec,
+ Any,
+ Generic,
+ ClassVar,
+ Optional,
+ Union,
+ assert_never,
+ cast,
+ overload,
+ override,
+)
+
+from ._const import CruNoValue, CruNotFound
+
+P = ParamSpec("P")
T = TypeVar("T")
O = TypeVar("O")
-R = TypeVar("R")
F = TypeVar("F")
-CanBeList = T | Iterable[T] | None
+CanBeList: TypeAlias = Iterable[T] | T | None
-OptionalIndex = int | None
-OptionalType = type | None
-ElementOperation = Callable[[T], Any] | None
-ElementPredicate = Callable[[T], bool]
-ElementTransformer = Callable[[T], R]
-SelfElementTransformer = ElementTransformer[T, T]
-AnyElementTransformer = ElementTransformer[Any, Any]
-OptionalElementOperation = ElementOperation | None
-OptionalElementTransformer = ElementTransformer | None
-OptionalSelfElementTransformer = ElementTransformer[T, T]
-OptionalAnyElementTransformer = AnyElementTransformer | None
+OptionalIndex: TypeAlias = int | None
+OptionalType: TypeAlias = type | None
+ElementOperation: TypeAlias = Callable[[T], Any]
+ElementPredicate: TypeAlias = Callable[[T], bool]
+ElementTransformer: TypeAlias = Callable[[T], O]
+SelfElementTransformer: TypeAlias = ElementTransformer[T, T]
+AnyElementTransformer: TypeAlias = ElementTransformer[Any, Any]
-def _flatten_with_func(o: T, max_depth: int, is_leave: ElementPredicate[T],
- get_children: SelfElementTransformer[T], depth: int = 0) -> Iterable[T]:
+def flatten_with_func(
+ o: T,
+ max_depth: int,
+ is_leave: ElementPredicate[T],
+ get_children: ElementTransformer[T, Iterable[T]],
+ depth: int = 0,
+) -> Iterable[T]:
if depth == max_depth or is_leave(o):
yield o
return
for child in get_children(o):
- yield from _flatten_with_func(child, max_depth, is_leave, get_children, depth + 1)
+ yield from flatten_with_func(
+ child, max_depth, is_leave, get_children, depth + 1
+ )
-class _Action(Enum):
+class _StepActionKind(Enum):
SKIP = 0
+ # TODO: Rename this
SEND = 1
STOP = 2
AGGREGATE = 3
@dataclass
-class _Result(Generic[T]):
- Action: ClassVar[type[_Action]] = _Action
+class _StepAction(Generic[T]):
+ value: Iterable[_StepAction[T]] | T | None
+ kind: _StepActionKind
+
+ @property
+ def non_aggregate_value(self) -> T:
+ assert self.kind != _StepActionKind.AGGREGATE
+ return cast(T, self.value)
+
+ @staticmethod
+ def skip() -> _StepAction[T]:
+ return _StepAction(None, _StepActionKind.SKIP)
- value: T | O | None
- action: Action
+ @staticmethod
+ def send(value: T | None) -> _StepAction[T]:
+ return _StepAction(value, _StepActionKind.SEND)
@staticmethod
- def skip() -> "_Result"[T]:
- return _Result(None, _Action.SKIP)
+ def stop(value: T | None = None) -> _StepAction[T]:
+ return _StepAction(value, _StepActionKind.STOP)
+
+ @staticmethod
+ def aggregate(*results: _StepAction[T]) -> _StepAction[T]:
+ return _StepAction(results, _StepActionKind.AGGREGATE)
+
+ @staticmethod
+ def send_last(value: Any) -> _StepAction[T]:
+ return _StepAction.aggregate(_StepAction.send(value), _StepAction.stop())
+
+ def flatten(self) -> Iterable[_StepAction[T]]:
+ return flatten_with_func(
+ self,
+ -1,
+ lambda r: r.kind != _StepActionKind.AGGREGATE,
+ lambda r: cast(Iterable[_StepAction[T]], r.value),
+ )
+
+
+_r_skip = _StepAction.skip
+_r_send = _StepAction.send
+_r_stop = _StepAction.stop
+_r_send_last = _StepAction.send_last
+_r_aggregate = _StepAction.aggregate
+
+
+_GeneralStepAction: TypeAlias = _StepAction[T] | T | None
+_GeneralStepActionConverter: TypeAlias = Callable[
+ [_GeneralStepAction[T]], _StepAction[T]
+]
+_IterateOperation = Callable[[T, int], _GeneralStepAction[O]]
+_IteratePreHook = Callable[[Iterable[T]], _GeneralStepAction[O]]
+_IteratePostHook = Callable[[int], _GeneralStepAction[O]]
+
+
+class CruGenericIterableMeta:
+ StepActionKind = _StepActionKind
+ StepAction = _StepAction
+ GeneralStepAction = _GeneralStepAction
+ GeneralStepActionConverter = _GeneralStepActionConverter
+ IterateOperation = _IterateOperation
+ IteratePreHook = _IteratePreHook
+ IteratePostHook = _IteratePostHook
@staticmethod
- def send(value: Any) -> "_Result"[T]:
- return _Result(value, _Action.SEND)
+ def _non_result_to_send(value: O | None) -> _StepAction[O]:
+ return _StepAction.send(value)
@staticmethod
- def stop(value: Any = None) -> "_Result"[T]:
- return _Result(value, _Action.STOP)
+ def _non_result_to_stop(value: O | None) -> _StepAction[O]:
+ return _StepAction.stop(value)
@staticmethod
- def aggregate(*result: "_Result"[T]) -> "_Result"[T]:
- return _Result(result, _Action.AGGREGATE)
+ def _none_pre_iterate() -> _StepAction[O]:
+ return _r_skip()
@staticmethod
- def send_last(value: Any) -> "_Result"[T]:
- return _Result.aggregate(_Result.send(value), _Result.stop())
+ def _none_post_iterate(
+ _index: int,
+ ) -> _StepAction[O]:
+ return _r_skip()
- def flatten(self) -> Iterable["_Result"[T]]:
- return _flatten_with_func(self, -1, lambda r: r.action != _Action.AGGREGATE, lambda r: r.value)
+ def iterate(
+ self,
+ operation: _IterateOperation[T, O],
+ fallback_return: O,
+ pre_iterate: _IteratePreHook[T, O],
+ post_iterate: _IteratePostHook[O],
+ convert_non_result: Callable[[O | None], _StepAction[O]],
+ ) -> Generator[O, None, O]:
+ pre_result = pre_iterate(self._iterable)
+ if not isinstance(pre_result, _StepAction):
+ real_pre_result = convert_non_result(pre_result)
+ for r in real_pre_result.flatten():
+ if r.kind == _StepActionKind.STOP:
+ return r.non_aggregate_value
+ elif r.kind == _StepActionKind.SEND:
+ yield r.non_aggregate_value
+ for index, element in enumerate(self._iterable):
+ result = operation(element, index)
+ if not isinstance(result, _StepAction):
+ real_result = convert_non_result(result)
+ for r in real_result.flatten():
+ if r.kind == _StepActionKind.STOP:
+ return r.non_aggregate_value
+ elif r.kind == _StepActionKind.SEND:
+ yield r.non_aggregate_value
+ else:
+ continue
-_r_skip = _Result.skip
-_r_send = _Result.send
-_r_stop = _Result.stop
-_r_send_last = _Result.send_last
-_r_aggregate = _Result.aggregate
+ post_result = post_iterate(index + 1)
+ if not isinstance(post_result, _StepAction):
+ real_post_result = convert_non_result(post_result)
+ for r in real_post_result.flatten():
+ if r.kind == _StepActionKind.STOP:
+ return r.non_aggregate_value
+ elif r.kind == _StepActionKind.SEND:
+ yield r.non_aggregate_value
+
+ return fallback_return
+
+ def _new(
+ self,
+ operation: _IterateOperation,
+ fallback_return: O,
+ /,
+ pre_iterate: _IteratePreHook[T, O] | None = None,
+ post_iterate: _IteratePostHook[O] | None = None,
+ ) -> CruIterableWrapper:
+ return CruIterableWrapper(
+ self.iterate(
+ operation,
+ fallback_return,
+ pre_iterate or CruIterableWrapper._none_pre_iterate,
+ post_iterate or CruIterableWrapper._none_post_iterate,
+ CruIterableWrapper._non_result_to_send,
+ ),
+ self._create_new_upstream(),
+ )
+
+ def _result(
+ self,
+ operation: _IterateOperation,
+ fallback_return: O,
+ /,
+ result_transform: SelfElementTransformer[O] | None = None,
+ pre_iterate: _IteratePreHook[T, O] | None = None,
+ post_iterate: _IteratePostHook[O] | None = None,
+ ) -> O:
+ try:
+ for _ in self.iterate(
+ operation,
+ fallback_return,
+ pre_iterate or CruIterableWrapper._none_pre_iterate,
+ post_iterate or CruIterableWrapper._none_post_iterate,
+ CruIterableWrapper._non_result_to_stop,
+ ):
+ pass
+ except StopIteration as stop:
+ return (
+ stop.value if result_transform is None else result_transform(stop.value)
+ )
+ raise RuntimeError("Should not reach here")
-class _Defaults:
+class IterDefaultResults:
@staticmethod
def true(_):
return True
@@ -90,35 +240,30 @@ class _Defaults:
@staticmethod
def not_found(_):
- return CRU_NOT_FOUND
-
-
-def _default_upstream() -> Iterable[Iterable]:
- return iter([])
-
-
-CruIterableUpstream = Iterable[Iterable]
-CruIterableOptionalUpstream = CruIterableUpstream | None
+ return CruNotFound.VALUE
class CruIterableCreators:
@staticmethod
- def with_(o: Any, /, upstreams: CruIterableOptionalUpstream = _default_upstream()) -> "CruIterableWrapper":
- return CruIterableWrapper(iter(o), upstreams)
+ def with_(o: Any) -> CruIterableWrapper:
+ return CruIterableWrapper(iter(o))
@staticmethod
- def empty(upstreams: CruIterableOptionalUpstream = _default_upstream()) -> "CruIterableWrapper":
- return CruIterableCreators.with_([], upstreams)
+ def empty() -> CruIterableWrapper:
+ return CruIterableCreators.with_([])
@staticmethod
- def range(a, b=None, c=None, /, upstreams: CruIterableOptionalUpstream = _default_upstream()) -> \
- "CruIterableWrapper"[int]:
+ def range(
+ a,
+ b=None,
+ c=None,
+ ) -> CruIterableWrapper[int]:
args = [arg for arg in [a, b, c] if arg is not None]
- return CruIterableCreators.with_(range(*args), upstreams)
+ return CruIterableCreators.with_(range(*args))
@staticmethod
- def unite(*args: T, upstreams: CruIterableOptionalUpstream = _default_upstream()) -> "CruIterableWrapper"[T]:
- return CruIterableCreators.with_(args, upstreams)
+ def unite(*args: T) -> CruIterableWrapper[T]:
+ return CruIterableCreators.with_(args)
@staticmethod
def _concat(*iterables: Iterable) -> Iterable:
@@ -126,138 +271,95 @@ class CruIterableCreators:
yield from iterable
@staticmethod
- def concat(*iterables: Iterable,
- upstreams: CruIterableOptionalUpstream = _default_upstream()) -> "CruIterableWrapper":
- return CruIterableWrapper(CruIterableCreators._concat(*iterables), upstreams)
+ def concat(*iterables: Iterable) -> CruIterableWrapper:
+ return CruIterableWrapper(CruIterableCreators._concat(*iterables))
class CruIterableWrapper(Generic[T]):
- Upstream = CruIterableUpstream
- OptionalUpstream = CruIterableOptionalUpstream
- _Result = _Result[T]
- _Operation = Callable[[T, int], _Result | Any | None]
- def __init__(self, iterable: Iterable[T], /, upstreams: OptionalUpstream = _default_upstream()) -> None:
+ def __init__(
+ self,
+ iterable: Iterable[T],
+ ) -> None:
self._iterable = iterable
- self._upstreams = None if upstreams is None else list(upstreams)
+
+ def __iter__(self):
+ return self._iterable.__iter__()
@property
def me(self) -> Iterable[T]:
return self._iterable
- # TODO: Return Type
- @property
- def my_upstreams(self) -> Optional["CruIterableWrapper"]:
- if self._upstreams is None:
- return None
- return CruIterableWrapper(iter(self._upstreams))
-
- def disable_my_upstreams(self) -> "CruIterableWrapper"[T]:
- return CruIterableWrapper(self._iterable, None)
-
- def clear_my_upstreams(self) -> "CruIterableWrapper"[T]:
- return CruIterableWrapper(self._iterable)
-
- def _create_upstreams_prepend_self(self) -> Upstream:
- yield self._iterable
- yield self.my_upstreams
-
- # TODO: Return Type
- def _create_new_upstreams(self, append: bool = True) -> Optional["CruIterableWrapper"]:
- if not append: return self.my_upstreams
- if self.my_upstreams is None:
- return None
- return CruIterableWrapper(self._create_upstreams_prepend_self())
-
- def clone_me(self, /, update_upstreams: bool = True) -> "CruIterableWrapper"[T]:
- return CruIterableWrapper(self._iterable, self._create_new_upstreams(update_upstreams))
-
- def replace_me_with(self, iterable: Iterable[O], /, update_upstreams: bool = True) -> "CruIterableWrapper"[O]:
- return CruIterableCreators.with_(iterable, upstreams=self._create_new_upstreams(update_upstreams))
-
- def replace_me_with_empty(self, /, update_upstreams: bool = True) -> "CruIterableWrapper"[O]:
- return CruIterableCreators.empty(upstreams=self._create_new_upstreams(update_upstreams))
-
- def replace_me_with_range(self, a, b=None, c=None, /, update_upstreams: bool = True) -> "CruIterableWrapper"[int]:
- return CruIterableCreators.range(a, b, c, upstreams=self._create_new_upstreams(update_upstreams))
-
- def replace_me_with_unite(self, *args: O, update_upstreams: bool = True) -> "CruIterableWrapper"[O]:
- return CruIterableCreators.unite(*args, upstreams=self._create_new_upstreams(update_upstreams))
-
- def replace_me_with_concat(self, *iterables: Iterable, update_upstreams: bool = True) -> "CruIterableWrapper":
- return CruIterableCreators.concat(*iterables, upstreams=self._create_new_upstreams(update_upstreams))
-
- @staticmethod
- def _non_result_to_yield(value: Any | None) -> _Result:
- return _Result.stop(value)
-
- @staticmethod
- def _non_result_to_return(value: Any | None) -> _Result:
- return _Result.stop(value)
+ def replace_me_with(self, iterable: Iterable[O]) -> CruIterableWrapper[O]:
+ return CruIterableCreators.with_(iterable)
- def _real_iterate(self, operation: _Operation,
- convert_non_result: Callable[[Any | None], _Result]) -> Iterable:
+ def replace_me_with_empty(self) -> CruIterableWrapper[O]:
+ return CruIterableCreators.empty()
- for index, element in enumerate(self._iterable):
- result = operation(element, index)
- if not isinstance(result, _Result):
- result = convert_non_result(result)
- for result in result.flatten():
- if result.action == _Result.Action.STOP:
- return result.value
- elif result.action == _Result.Action.SEND:
- yield result.value
- else:
- continue
+ def replace_me_with_range(self, a, b=None, c=None) -> CruIterableWrapper[int]:
+ return CruIterableCreators.range(a, b, c)
- def _new(self, operation: _Operation) -> "CruIterableWrapper":
- return CruIterableWrapper(self._real_iterate(operation, CruIterableWrapper._non_result_to_yield),
- self._create_new_upstreams())
+ def replace_me_with_unite(self, *args: O) -> CruIterableWrapper[O]:
+ return CruIterableCreators.unite(*args)
- def _result(self, operation: _Operation,
- result_transform: OptionalElementTransformer[T, T | O] = None) -> T | O:
- try:
- self._real_iterate(operation, CruIterableWrapper._non_result_to_return)
- except StopIteration as stop:
- return stop.value if result_transform is None else result_transform(stop.value)
+ def replace_me_with_concat(self, *iterables: Iterable) -> CruIterableWrapper:
+ return CruIterableCreators.concat(*iterables)
@staticmethod
- def _make_set(iterable: Iterable, discard: Iterable | None) -> set:
+ def _make_set(iterable: Iterable[O], discard: Iterable[Any] | None) -> set[O]:
s = set(iterable)
if discard is not None:
s = s - set(discard)
return s
@staticmethod
- def _make_list(iterable: Iterable, discard: Iterable | None) -> list:
- if discard is None: return list(iterable)
+ def _make_list(iterable: Iterable[O], discard: Iterable[Any] | None) -> list[O]:
+ if discard is None:
+ return list(iterable)
return [v for v in iterable if v not in discard]
- # noinspection PyMethodMayBeStatic
- def _help_make_set(self, iterable: Iterable, discard: Iterable | None = iter([None])) -> set:
+ def _help_make_set(
+ self, iterable: Iterable[O], discard: Iterable[Any] | None
+ ) -> set[O]:
return CruIterableWrapper._make_set(iterable, discard)
- # noinspection PyMethodMayBeStatic
- def _help_make_list(self, iterable: Iterable, discard: Iterable | None = iter([None])) -> list:
+ def _help_make_list(
+ self, iterable: Iterable[O], discard: Iterable[Any] | None
+ ) -> list[O]:
return CruIterableWrapper._make_list(iterable, discard)
- def to_set(self, discard: Iterable | None = None) -> set[T]:
+ def to_set(self, discard: Iterable[Any] | None = None) -> set[T]:
return CruIterableWrapper._make_set(self.me, discard)
- def to_list(self, discard: Iterable | None = None) -> list[T]:
+ def to_list(self, discard: Iterable[Any] | None = None) -> list[T]:
return CruIterableWrapper._make_list(self.me, discard)
- def copy(self) -> "CruIterableWrapper":
- return CruIterableWrapper(iter(self.to_list()), self._create_new_upstreams())
+ def copy(self) -> CruIterableWrapper:
+ return CruIterableWrapper(iter(self.to_list()), self._create_new_upstream())
- def concat(self, *iterable: Iterable[T]) -> "CruIterableWrapper":
- return self.replace_me_with_concat(self.me, *iterable)
+ def new_start(
+ self, other: Iterable[O], /, clear_upstream: bool = False
+ ) -> CruIterableWrapper[O]:
+ return CruIterableWrapper(
+ other, None if clear_upstream else self._create_new_upstream()
+ )
+
+ @overload
+ def concat(self) -> Self: ...
+
+ @overload
+ def concat(
+ self, *iterable: Iterable[Any], last: Iterable[O]
+ ) -> CruIterableWrapper[O]: ...
+
+ def concat(self, *iterable: Iterable[Any]) -> CruIterableWrapper[Any]: # type: ignore
+ return self.new_start(CruIterableCreators.concat(self.me, *iterable))
def all(self, predicate: ElementPredicate[T]) -> bool:
"""
partial
"""
- return self._result(lambda v, _: predicate(v) and None, _Defaults.true)
+ return self._result(lambda v, _: predicate(v) and None, IterDefaultResults.true)
def all_isinstance(self, *types: OptionalType) -> bool:
"""
@@ -270,21 +372,23 @@ class CruIterableWrapper(Generic[T]):
"""
partial
"""
- return self._result(lambda v, _: predicate(v) or None, _Defaults.false)
+ return self._result(lambda v, _: predicate(v) or None, IterDefaultResults.false)
- def number(self) -> "CruIterableWrapper":
+ def number(self) -> CruIterableWrapper:
"""
partial
"""
return self._new(lambda _, i: i)
- def take(self, predicate: ElementPredicate[T]) -> "CruIterableWrapper":
+ def take(self, predicate: ElementPredicate[T]) -> CruIterableWrapper:
"""
complete
"""
return self._new(lambda v, _: _r_send(v) if predicate(v) else None)
- def transform(self, *transformers: OptionalElementTransformer) -> "CruIterableWrapper":
+ def transform(
+ self, *transformers: OptionalElementTransformer
+ ) -> CruIterableWrapper:
"""
complete
"""
@@ -297,7 +401,7 @@ class CruIterableWrapper(Generic[T]):
return self._new(_transform_element)
- def take_n(self, max_count: int, neg_is_clone: bool = True) -> "CruIterableWrapper":
+ def take_n(self, max_count: int, neg_is_clone: bool = True) -> CruIterableWrapper:
"""
partial
"""
@@ -308,17 +412,23 @@ class CruIterableWrapper(Generic[T]):
raise ValueError("max_count must be 0 or positive.")
elif max_count == 0:
return self.drop_all()
- return self._new(lambda v, i: _r_send(v) if i < max_count - 1 else _r_send_last(v))
+ return self._new(
+ lambda v, i: _r_send(v) if i < max_count - 1 else _r_send_last(v)
+ )
- def take_by_indices(self, *indices: OptionalIndex) -> "CruIterableWrapper":
+ def take_by_indices(self, *indices: OptionalIndex) -> CruIterableWrapper:
"""
partial
"""
indices = self._help_make_set(indices)
max_index = max(indices)
- return self.take_n(max_index + 1)._new(lambda v, i: _r_send(v) if i in indices else None)
+ return self.take_n(max_index + 1)._new(
+ lambda v, i: _r_send(v) if i in indices else None
+ )
- def single_or(self, fallback: Any | None = CRU_NOT_FOUND) -> T | Any | CRU_NOT_FOUND:
+ def single_or(
+ self, fallback: Any | None = CRU_NOT_FOUND
+ ) -> T | Any | CRU_NOT_FOUND:
"""
partial
"""
@@ -335,14 +445,18 @@ class CruIterableWrapper(Generic[T]):
else:
return fallback
- def first_or(self, predicate: ElementPredicate[T], fallback: Any | None = CRU_NOT_FOUND) -> T | CRU_NOT_FOUND:
+ def first_or(
+ self, predicate: ElementPredicate[T], fallback: Any | None = CRU_NOT_FOUND
+ ) -> T | CRU_NOT_FOUND:
"""
partial
"""
result_iterable = self.take_n(1).single_or()
@staticmethod
- def first_index(iterable: Iterable[T], predicate: ElementPredicate[T]) -> int | CRU_NOT_FOUND:
+ def first_index(
+ iterable: Iterable[T], predicate: ElementPredicate[T]
+ ) -> int | CRU_NOT_FOUND:
"""
partial
"""
@@ -351,7 +465,9 @@ class CruIterableWrapper(Generic[T]):
return index
@staticmethod
- def take_indices(iterable: Iterable[T], predicate: ElementPredicate[T]) -> Iterable[int]:
+ def take_indices(
+ iterable: Iterable[T], predicate: ElementPredicate[T]
+ ) -> Iterable[int]:
"""
complete
"""
@@ -360,8 +476,12 @@ class CruIterableWrapper(Generic[T]):
yield index
@staticmethod
- def flatten(o, max_depth=-1, is_leave: ElementPredicate | None = None,
- get_children: OptionalElementTransformer = None) -> Iterable:
+ def flatten(
+ o,
+ max_depth=-1,
+ is_leave: ElementPredicate | None = None,
+ get_children: OptionalElementTransformer = None,
+ ) -> Iterable:
"""
complete
"""
@@ -369,7 +489,9 @@ class CruIterableWrapper(Generic[T]):
is_leave = lambda v: not isinstance(v, Iterable)
if get_children is None:
get_children = lambda v: v
- return CruIterableWrapper._flatten_with_func(o, max_depth, is_leave, get_children)
+ return CruIterableWrapper._flatten_with_func(
+ o, max_depth, is_leave, get_children
+ )
@staticmethod
def skip_by_indices(iterable: Iterable[T], *indices: OptionalIndex) -> Iterable[T]:
@@ -390,7 +512,7 @@ class CruIterableWrapper(Generic[T]):
if not predicate(element):
yield element
- def drop_all(self) -> "CruIterableWrapper":
+ def drop_all(self) -> CruIterableWrapper:
return self.replace_me_with_empty()
@staticmethod
@@ -407,7 +529,8 @@ class CruIterableWrapper(Generic[T]):
@staticmethod
def foreach(iterable: Iterable[T], *f: OptionalElementOperation[T]) -> None:
- if len(f) == 0: return
+ if len(f) == 0:
+ return
for v in iterable:
for f_ in f:
if f_ is not None:
@@ -415,7 +538,8 @@ class CruIterableWrapper(Generic[T]):
@staticmethod
def make(v: CanBeList[T], /, none_to_empty_list: bool = True) -> list[T]:
- if v is None and none_to_empty_list: return []
+ if v is None and none_to_empty_list:
+ return []
return list(v) if isinstance(v, Iterable) else [v]
@@ -457,7 +581,9 @@ class ListOperations:
return _God.new(iterable, lambda v, _: _God.yield_(v) if predicate(v) else None)
@staticmethod
- def transform(iterable: Iterable[T], *transformers: OptionalElementTransformer) -> Iterable:
+ def transform(
+ iterable: Iterable[T], *transformers: OptionalElementTransformer
+ ) -> Iterable:
"""
complete
"""
@@ -479,7 +605,9 @@ class ListOperations:
return iterable
elif n == 0:
return []
- return range(n)._god_yield(iterable, lambda v, i: _yield(v) if i < n else _return())
+ return range(n)._god_yield(
+ iterable, lambda v, i: _yield(v) if i < n else _return()
+ )
@staticmethod
def take_by_indices(iterable: Iterable[T], *indices: OptionalIndex) -> Iterable[T]:
@@ -502,7 +630,9 @@ class ListOperations:
return CRU_NOT_FOUND
@staticmethod
- def first_index(iterable: Iterable[T], predicate: ElementPredicate[T]) -> int | CRU_NOT_FOUND:
+ def first_index(
+ iterable: Iterable[T], predicate: ElementPredicate[T]
+ ) -> int | CRU_NOT_FOUND:
"""
partial
"""
@@ -511,7 +641,9 @@ class ListOperations:
return index
@staticmethod
- def take_indices(iterable: Iterable[T], predicate: ElementPredicate[T]) -> Iterable[int]:
+ def take_indices(
+ iterable: Iterable[T], predicate: ElementPredicate[T]
+ ) -> Iterable[int]:
"""
complete
"""
@@ -567,7 +699,8 @@ class ListOperations:
@staticmethod
def foreach(iterable: Iterable[T], *f: OptionalElementOperation[T]) -> None:
- if len(f) == 0: return
+ if len(f) == 0:
+ return
for v in iterable:
for f_ in f:
if f_ is not None:
@@ -575,7 +708,8 @@ class ListOperations:
@staticmethod
def make(v: CanBeList[T], /, none_to_empty_list: bool = True) -> list[T]:
- if v is None and none_to_empty_list: return []
+ if v is None and none_to_empty_list:
+ return []
return list(v) if isinstance(v, Iterable) else [v]
@@ -629,7 +763,9 @@ class CruList(list, Generic[T]):
def transform(self, *f: OptionalElementTransformer) -> "CruList"[Any]:
return CruList(ListOperations.transform(self, *f))
- def transform_if(self, f: OptionalElementTransformer, p: ElementPredicate[T]) -> "CruList"[Any]:
+ def transform_if(
+ self, f: OptionalElementTransformer, p: ElementPredicate[T]
+ ) -> "CruList"[Any]:
return CruList(ListOperations.transform_if(self, f, p))
def remove_by_indices(self, *index: int) -> "CruList"[T]:
@@ -670,7 +806,9 @@ class CruInplaceList(CruList, Generic[T]):
def transform(self, *f: OptionalElementTransformer) -> "CruInplaceList"[Any]:
return self.reset(super().transform(*f))
- def transform_if(self, f: OptionalElementTransformer, p: ElementPredicate[T]) -> "CruInplaceList"[Any]:
+ def transform_if(
+ self, f: OptionalElementTransformer, p: ElementPredicate[T]
+ ) -> "CruInplaceList"[Any]:
return self.reset(super().transform_if(f, p))
def remove_by_indices(self, *index: int) -> "CruInplaceList"[T]:
@@ -682,7 +820,9 @@ class CruInplaceList(CruList, Generic[T]):
def remove_all_value(self, *r: Any) -> "CruInplaceList"[T]:
return self.reset(super().remove_all_value(*r))
- def replace_all_value(self, old_value: Any, new_value: R) -> "CruInplaceList"[T | R]:
+ def replace_all_value(
+ self, old_value: Any, new_value: R
+ ) -> "CruInplaceList"[T | R]:
return self.reset(super().replace_all_value(old_value, new_value))
@staticmethod
@@ -696,7 +836,9 @@ K = TypeVar("K")
class CruUniqueKeyInplaceList(Generic[T, K]):
KeyGetter = Callable[[T], K]
- def __init__(self, get_key: KeyGetter, *, before_add: Callable[[T], T] | None = None):
+ def __init__(
+ self, get_key: KeyGetter, *, before_add: Callable[[T], T] | None = None
+ ):
super().__init__()
self._get_key = get_key
self._before_add = before_add
@@ -733,7 +875,8 @@ class CruUniqueKeyInplaceList(Generic[T, K]):
def try_remove(self, k: K) -> bool:
i = self._l.find_index_if(lambda v: k == self._get_key(v))
- if i is CRU_NOT_FOUND: return False
+ if i is CRU_NOT_FOUND:
+ return False
self._l.remove_by_indices(i)
return True
diff --git a/tools/cru-py/cru/util/_type.py b/tools/cru-py/cru/_util/_type.py
index dc50def..dc50def 100644
--- a/tools/cru-py/cru/util/_type.py
+++ b/tools/cru-py/cru/_util/_type.py
diff --git a/tools/cru-py/cru/attr.py b/tools/cru-py/cru/attr.py
index 32cca8d..a52585a 100644
--- a/tools/cru-py/cru/attr.py
+++ b/tools/cru-py/cru/attr.py
@@ -3,7 +3,7 @@ from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
from typing import Any, ClassVar
-from .util import CanBeList, TypeSet, F, L, WF, CruUniqueKeyInplaceList, CRU_NOT_FOUND, CRU_USE_DEFAULT, \
+from ._util import CanBeList, TypeSet, F, L, WF, CruUniqueKeyInplaceList, CRU_NOT_FOUND, CRU_USE_DEFAULT, \
CRU_DONT_CHANGE, CRU_PLACEHOLDER
diff --git a/tools/cru-py/cru/excp.py b/tools/cru-py/cru/excp.py
index 358ad90..9ea204e 100644
--- a/tools/cru-py/cru/excp.py
+++ b/tools/cru-py/cru/excp.py
@@ -1,7 +1,7 @@
from typing import Any
from .attr import CruAttrDefRegistry, CruAttr, CruAttrTable
-from .util import CRU_NOT_FOUND, CruList, CRU_USE_DEFAULT
+from ._util import CRU_NOT_FOUND, CruList, CRU_USE_DEFAULT
CRU_EXCEPTION_ATTR_DEF_REGISTRY = CruAttrDefRegistry()
diff --git a/tools/cru-py/cru/service/docker.py b/tools/cru-py/cru/service/docker.py
index a57a246..5958f4f 100644
--- a/tools/cru-py/cru/service/docker.py
+++ b/tools/cru-py/cru/service/docker.py
@@ -1,7 +1,7 @@
import shutil
import subprocess
-from ..util import L
+from .._util import L
class DockerController:
diff --git a/tools/cru-py/cru/util/__init__.py b/tools/cru-py/cru/util/__init__.py
deleted file mode 100644
index ecd9673..0000000
--- a/tools/cru-py/cru/util/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from typing import Any
-
-from ._const import cru_make_unique_object, cru_make_bool_unique_object, CRU_NOT_FOUND, CRU_USE_DEFAULT, \
- CRU_DONT_CHANGE, \
- CRU_PLACEHOLDER
-from ._func import CruFunction, CruFunctionMeta, CruRawFunctions, CruWrappedFunctions, CruFunctionGenerators
-from ._list import CruList, CruInplaceList, CruUniqueKeyInplaceList, ListOperations, CanBeList, ElementOperation, \
- ElementPredicate, ElementTransformer, OptionalElementOperation, ElementPredicate, OptionalElementTransformer
-from ._type import TypeSet
-
-F = CruFunction
-WF = CruWrappedFunctions
-FG = CruFunctionGenerators
-L = CruList
-
-
-__all__ = [
- "CRU_NOT_FOUND", "CRU_USE_DEFAULT", "CRU_DONT_CHANGE", "CRU_PLACEHOLDER",
- "CruFunction", "CruFunctionMeta", "CruRawFunctions", "CruWrappedFunctions", "CruFunctionGenerators",
- "CruList", "CruInplaceList", "CruUniqueKeyInplaceList", "ListOperations",
- "CanBeList", "ElementOperation", "ElementPredicate", "ElementTransformer",
- "OptionalElementOperation", "ElementPredicate", "OptionalElementTransformer",
- "TypeSet",
- "F", "WF", "FG", "L"
-]
diff --git a/tools/cru-py/cru/util/_const.py b/tools/cru-py/cru/util/_const.py
deleted file mode 100644
index 8140988..0000000
--- a/tools/cru-py/cru/util/_const.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from typing import Any
-
-from ._cru import CRU
-
-
-def cru_make_unique_object() -> Any:
- class _CruUnique:
- _i = False
-
- def __init__(self):
- if self._i:
- raise ValueError("_CruAttrNotSet is a singleton!")
- self._i = True
-
- def __copy__(self):
- return self
-
- def __eq__(self, other):
- return isinstance(other, _CruUnique)
-
- v = _CruUnique()
-
- return v
-
-
-def cru_make_bool_unique_object(b: bool) -> Any:
- class _CruBoolUnique:
- _i = False
-
- def __init__(self):
- super().__init__(b)
- if self._i:
- raise ValueError("_CruAttrNotSet is a singleton!")
- self._i = True
-
- def __copy__(self):
- return self
-
- def __eq__(self, other):
- return isinstance(other, _CruBoolUnique) or b == other
-
- def __bool__(self):
- return b
-
- v = _CruBoolUnique()
-
- return v
-
-
-CRU_NOT_FOUND = cru_make_bool_unique_object(False)
-CRU_USE_DEFAULT = cru_make_unique_object()
-CRU_DONT_CHANGE = cru_make_unique_object()
-CRU_PLACEHOLDER = cru_make_unique_object()
-
-CRU.add_objects(cru_make_unique_object, cru_make_bool_unique_object, CRU_NOT_FOUND, CRU_USE_DEFAULT,
- CRU_DONT_CHANGE, CRU_PLACEHOLDER)
diff --git a/tools/cru-py/cru/util/_func.py b/tools/cru-py/cru/util/_func.py
deleted file mode 100644
index d9f8044..0000000
--- a/tools/cru-py/cru/util/_func.py
+++ /dev/null
@@ -1,126 +0,0 @@
-from collections.abc import Callable
-from typing import TypeVar
-
-from ._cru import CRU
-from ._const import CRU_PLACEHOLDER
-
-T = TypeVar("T")
-
-_PLACEHOLDER = CRU_PLACEHOLDER
-
-class CruRawFunctions:
- @staticmethod
- def none(*_v, **_kwargs) -> None:
- return None
-
- @staticmethod
- def true(*_v, **_kwargs) -> True:
- return True
-
- @staticmethod
- def false(*_v, **_kwargs) -> False:
- return False
-
- @staticmethod
- def identity(v: T) -> T:
- return v
-
- @staticmethod
- def only_you(v: T, *_v, **_kwargs) -> T:
- return v
-
- @staticmethod
- def equal(a, b) -> bool:
- return a == b
-
- @staticmethod
- def not_equal(a, b) -> bool:
- return a != b
-
- @staticmethod
- def not_(v):
- return not v
-
-
-class CruFunctionMeta:
- @staticmethod
- def bind(func: Callable, *bind_args, **bind_kwargs) -> Callable:
- def bound_func(*args, **kwargs):
- popped = 0
- real_args = []
- for arg in bind_args:
- if arg is _PLACEHOLDER:
- real_args.append(args[popped])
- popped += 1
- else:
- real_args.append(arg)
- real_args.extend(args[popped:])
- return func(*real_args, **(bind_kwargs | kwargs))
-
- return bound_func
-
- @staticmethod
- def chain(*funcs: Callable) -> Callable:
- if len(funcs) == 0:
- raise ValueError("At least one function is required!")
-
- final_func = funcs[0]
- for func in funcs[1:]:
- func_copy = func
-
- def chained_func(*args, **kwargs):
- results = final_func(*args, **kwargs)
- results = results if isinstance(results, tuple) else (results,)
- return func_copy(*results)
-
- final_func = chained_func
-
- return final_func
-
-
-# Advanced Function Wrapper
-class CruFunction:
- def __init__(self, f: Callable):
- self._f = f
-
- @property
- def f(self) -> Callable:
- return self._f
-
- @property
- def func(self) -> Callable:
- return self.f
-
- def bind(self, *bind_args, **bind_kwargs) -> "CruFunction":
- self._f = CruFunctionMeta.bind(self._f, *bind_args, **bind_kwargs)
- return self
-
- def chain(self, *funcs: Callable) -> "CruFunction":
- self._f = CruFunctionMeta.chain(self._f, *funcs)
- return self
-
- def __call__(self, *args, **kwargs):
- return self._f(*args, **kwargs)
-
- @staticmethod
- def make_chain(base_func: Callable, *funcs: Callable) -> "CruFunction":
- return CruFunction(base_func).chain(*funcs)
-
-
-class CruWrappedFunctions:
- none = CruFunction(CruRawFunctions.none)
- true = CruFunction(CruRawFunctions.true)
- false = CruFunction(CruRawFunctions.false)
- identity = CruFunction(CruRawFunctions.identity)
- only_you = CruFunction(CruRawFunctions.only_you)
- equal = CruFunction(CruRawFunctions.equal)
- not_equal = CruFunction(CruRawFunctions.not_equal)
- not_ = CruFunction(CruRawFunctions.not_)
-
-
-class CruFunctionGenerators:
- @staticmethod
- def make_isinstance_of_types(*types: type) -> Callable:
- return CruFunction(lambda v: type(v) in types)
-
-CRU.add_objects(CruRawFunctions, CruFunctionMeta, CruFunction, CruWrappedFunctions, CruFunctionGenerators)
diff --git a/tools/cru-py/aio.py b/tools/cru-py/crupest/aio.py
index d5386f1..0a26146 100644
--- a/tools/cru-py/aio.py
+++ b/tools/cru-py/crupest/aio.py
@@ -12,18 +12,18 @@ from os.path import *
import argparse
import subprocess
from rich.prompt import Confirm
-from crupest.install_docker import *
-from crupest.path import *
-from crupest.nginx import *
-from crupest.config import *
-from crupest.check import *
-from crupest.backup import *
-from crupest.download_tools import *
-from crupest.test import *
-from crupest.dns import *
-from crupest.setup import *
-
-from crupest.tui import console
+from install_docker import *
+from path import *
+from nginx import *
+from config import *
+from check import *
+from backup import *
+from download_tools import *
+from test import *
+from dns import *
+from setup import *
+
+from tui import console
parser = argparse.ArgumentParser(
diff --git a/tools/cru-py/poetry.lock b/tools/cru-py/poetry.lock
new file mode 100644
index 0000000..0f74ba5
--- /dev/null
+++ b/tools/cru-py/poetry.lock
@@ -0,0 +1,642 @@
+# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
+
+[[package]]
+name = "attrs"
+version = "24.2.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
+ {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
+tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "black"
+version = "24.10.0"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
+ {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
+ {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
+ {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
+ {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
+ {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
+ {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
+ {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
+ {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
+ {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
+ {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
+ {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
+ {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
+ {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
+ {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
+ {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
+ {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
+ {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
+ {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
+ {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
+ {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
+ {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.10)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "cffi"
+version = "1.17.1"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+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"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
+ {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
+ {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
+ {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
+ {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
+ {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
+ {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
+ {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
+ {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
+ {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
+ {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
+ {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
+ {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
+ {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
+ {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
+ {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
+ {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
+ {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
+ {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
+ {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
+ {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
+ {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
+ {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
+ {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
+ {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
+ {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
+ {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
+ {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
+ {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
+ {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
+ {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
+ {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
+ {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
+ {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
+ {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
+ {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
+ {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
+ {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
+ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
+ {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+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"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "cryptography"
+version = "43.0.3"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
+ {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
+ {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"},
+ {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"},
+ {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"},
+ {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"},
+ {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"},
+ {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"},
+ {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"},
+ {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"},
+ {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"},
+ {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"},
+ {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"},
+ {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"},
+ {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"},
+ {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"},
+ {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"},
+ {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"},
+ {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"},
+ {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"},
+ {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"},
+ {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"},
+ {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"},
+ {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"},
+ {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"},
+ {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"},
+ {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"},
+]
+
+[package.dependencies]
+cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
+docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
+nox = ["nox"]
+pep8test = ["check-sdist", "click", "mypy", "ruff"]
+sdist = ["build"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
+test-randomorder = ["pytest-randomly"]
+
+[[package]]
+name = "flake8"
+version = "7.1.1"
+description = "the modular source code checker: pep8 pyflakes and co"
+optional = false
+python-versions = ">=3.8.1"
+files = [
+ {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"},
+ {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"},
+]
+
+[package.dependencies]
+mccabe = ">=0.7.0,<0.8.0"
+pycodestyle = ">=2.12.0,<2.13.0"
+pyflakes = ">=3.2.0,<3.3.0"
+
+[[package]]
+name = "flake8-bugbear"
+version = "24.10.31"
+description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
+optional = false
+python-versions = ">=3.8.1"
+files = [
+ {file = "flake8_bugbear-24.10.31-py3-none-any.whl", hash = "sha256:cccf786ccf9b2e1052b1ecfa80fb8f80832d0880425bcbd4cd45d3c8128c2683"},
+ {file = "flake8_bugbear-24.10.31.tar.gz", hash = "sha256:435b531c72b27f8eff8d990419697956b9fd25c6463c5ba98b3991591de439db"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+flake8 = ">=6.0.0"
+
+[package.extras]
+dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"]
+
+[[package]]
+name = "isort"
+version = "5.13.2"
+description = "A Python utility / library to sort Python imports."
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
+ {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
+]
+
+[package.extras]
+colors = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "jsonschema"
+version = "4.23.0"
+description = "An implementation of JSON Schema validation for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"},
+ {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+jsonschema-specifications = ">=2023.03.6"
+referencing = ">=0.28.4"
+rpds-py = ">=0.7.1"
+
+[package.extras]
+format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2024.10.1"
+description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"},
+ {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"},
+]
+
+[package.dependencies]
+referencing = ">=0.31.0"
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+optional = false
+python-versions = ">=3.8"
+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"},
+]
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark"]
+code-style = ["pre-commit (>=3.0,<4.0)"]
+compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
+linkify = ["linkify-it-py (>=1,<3)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+description = "McCabe checker, plugin for flake8"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
+[[package]]
+name = "mypy"
+version = "1.13.0"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
+ {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
+ {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
+ {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
+ {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
+ {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
+ {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
+ {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
+ {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
+ {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
+ {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
+ {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
+ {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
+ {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
+ {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
+ {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
+ {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
+ {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
+ {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
+ {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
+ {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
+ {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
+ {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
+ {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
+ {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
+ {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
+ {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
+ {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
+ {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
+ {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
+ {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
+ {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=1.0.0"
+typing-extensions = ">=4.6.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+faster-cache = ["orjson"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
+
+[[package]]
+name = "packaging"
+version = "24.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
+ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.6"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
+ {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.11.2)"]
+
+[[package]]
+name = "pycodestyle"
+version = "2.12.1"
+description = "Python style guide checker"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"},
+ {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"},
+]
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+description = "C parser in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+]
+
+[[package]]
+name = "pyflakes"
+version = "3.2.0"
+description = "passive checker of Python programs"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
+ {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
+]
+
+[[package]]
+name = "pygments"
+version = "2.18.0"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
+ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "referencing"
+version = "0.35.1"
+description = "JSON Referencing + Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"},
+ {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+rpds-py = ">=0.7.0"
+
+[[package]]
+name = "rich"
+version = "13.9.4"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
+ {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
+]
+
+[package.dependencies]
+markdown-it-py = ">=2.2.0"
+pygments = ">=2.13.0,<3.0.0"
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<9)"]
+
+[[package]]
+name = "rpds-py"
+version = "0.21.0"
+description = "Python bindings to Rust's persistent data structures (rpds)"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"},
+ {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"},
+ {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"},
+ {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"},
+ {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"},
+ {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"},
+ {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"},
+ {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"},
+ {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"},
+ {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"},
+ {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"},
+ {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"},
+ {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"},
+ {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"},
+ {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"},
+ {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"},
+ {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"},
+ {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"},
+ {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"},
+ {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"},
+ {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"},
+ {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"},
+ {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"},
+ {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"},
+ {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"},
+ {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"},
+ {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"},
+ {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"},
+ {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"},
+ {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"},
+ {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"},
+ {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"},
+ {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"},
+ {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"},
+ {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"},
+ {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"},
+ {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"},
+ {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"},
+ {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"},
+ {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"},
+ {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"},
+ {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"},
+ {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"},
+ {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"},
+ {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"},
+ {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"},
+ {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"},
+ {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"},
+ {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"},
+ {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"},
+ {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"},
+ {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"},
+ {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"},
+ {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"},
+ {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"},
+ {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"},
+ {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"},
+ {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"},
+ {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"},
+ {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"},
+ {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"},
+ {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"},
+ {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"},
+ {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"},
+ {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"},
+ {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"},
+ {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"},
+ {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.11"
+content-hash = "86a0266bee7cb43e7f1f5883cefd0b2a83dc1c4584bfc6281fb7c14856a28957"
diff --git a/tools/cru-py/pyproject.toml b/tools/cru-py/pyproject.toml
new file mode 100644
index 0000000..bbf7873
--- /dev/null
+++ b/tools/cru-py/pyproject.toml
@@ -0,0 +1,28 @@
+[tool.poetry]
+package-mode = false
+name = "cru"
+version = "0.1.0"
+description = ""
+authors = ["Yuqian Yang <crupest@crupest.life>"]
+license = "MIT"
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+rich = "^13.9.4"
+jsonschema = "^4.23.0"
+cryptography = "^43.0.3"
+
+[tool.poetry.group.dev.dependencies]
+black = "^24.10.0"
+isort = "^5.13.2"
+flake8 = "^7.1.1"
+flake8-bugbear = "^24.10.31"
+mypy = "^1.13.0"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.isort]
+profile = "black"
diff --git a/tools/cru-py/requirements.txt b/tools/cru-py/requirements.txt
deleted file mode 100644
index 2fb5657..0000000
--- a/tools/cru-py/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rich
-jsonschema
-cryptography