diff --git a/.gitignore b/.gitignore index 89ac765..f06314c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target +.idea # Byte-compiled / optimized / DLL files __pycache__/ @@ -162,4 +163,5 @@ devenv.local.nix cqcpython-secret.txt -*.DS_Store \ No newline at end of file +*.DS_Store +*.code-workspace diff --git a/python/.gitignore b/python/.gitignore index 61e9164..f5b25c4 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -1,2 +1 @@ ./venv -tierkreis/core/protos/ diff --git a/python/examples/launchers/numerical-worker b/python/examples/launchers/numerical-worker new file mode 100755 index 0000000..5aeeefd --- /dev/null +++ b/python/examples/launchers/numerical-worker @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +printf "\n\n------ NUMERICAL WORKER ------" + +LAUNCHER_DIR=$(dirname "$0") +uv run "$LAUNCHER_DIR/../numerical-worker" $1 \ No newline at end of file diff --git a/python/examples/numerical-worker/__main__.py b/python/examples/numerical-worker/__main__.py new file mode 100644 index 0000000..0c52bf8 --- /dev/null +++ b/python/examples/numerical-worker/__main__.py @@ -0,0 +1,105 @@ +import json +from logging import getLogger +from pathlib import Path +from sys import argv + +from pydantic import BaseModel + +logger = getLogger(__name__) + + +class NodeDefinition(BaseModel): + function_name: str + inputs: dict[str, Path] + outputs: dict[str, Path] + done_path: Path + + +def iadd(a: int, b: int) -> int: + logger.debug(f"iadd {a} {b}") + return a + b + + +def itimes(a: int, b: int) -> int: + logger.debug(f"itimes {a} {b}") + return a * b + + +def igt(a: int, b: int) -> bool: + logger.debug(f"igt {a} {b}") + return a > b + + +def run(node_definition: NodeDefinition): + logger.debug(node_definition) + if node_definition.function_name == "iadd": + with open(node_definition.inputs["a"], "rb") as fh: + a: int = json.loads(fh.read()) + with open(node_definition.inputs["b"], "rb") as fh: + b: int = json.loads(fh.read()) + + c = iadd(a, b) + + with open(node_definition.outputs["value"], "w+") as fh: + fh.write(json.dumps(c)) + + elif node_definition.function_name == "itimes": + with open(node_definition.inputs["a"], "rb") as fh: + a: int = json.loads(fh.read()) + with open(node_definition.inputs["b"], "rb") as fh: + b: int = json.loads(fh.read()) + + c = itimes(a, b) + + with open(node_definition.outputs["value"], "w+") as fh: + fh.write(json.dumps(c)) + + elif node_definition.function_name == "igt": + with open(node_definition.inputs["a"], "rb") as fh: + a: int = json.loads(fh.read()) + with open(node_definition.inputs["b"], "rb") as fh: + b: int = json.loads(fh.read()) + + c = igt(a, b) + + with open(node_definition.outputs["value"], "w+") as fh: + fh.write(json.dumps(c)) + + elif node_definition.function_name == "and": + with open(node_definition.inputs["a"], "rb") as fh: + a = json.loads(fh.read()) + with open(node_definition.inputs["b"], "rb") as fh: + b = json.loads(fh.read()) + + c = a and b + + with open(node_definition.outputs["value"], "w+") as fh: + fh.write(json.dumps(c)) + + elif node_definition.function_name == "id": + with open(node_definition.inputs["value"], "rb") as fh: + value = json.loads(fh.read()) + + with open(node_definition.outputs["value"], "w+") as fh: + fh.write(json.dumps(value)) + + elif node_definition.function_name == "fold_values": + values = [] + for i, path in enumerate(node_definition.inputs.values()): + with open(path, "rb") as fh: + values.append(json.loads(fh.read())) + + with open(node_definition.outputs["value"], "w+") as fh: + fh.write(json.dumps(values)) + + else: + raise ValueError(f"function name {node_definition.function_name} not found") + node_definition.done_path.touch() + + +if __name__ == "__main__": + worker_definition_path = argv[1] + with open(worker_definition_path, "r") as fh: + node_definition = NodeDefinition(**json.loads(fh.read())) + + run(node_definition) diff --git a/python/examples/numerical-worker/pyproject.toml b/python/examples/numerical-worker/pyproject.toml new file mode 100644 index 0000000..6e2a088 --- /dev/null +++ b/python/examples/numerical-worker/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "numerical-worker" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10, <3.13" +dependencies = [] diff --git a/python/examples/numerical-worker/uv.lock b/python/examples/numerical-worker/uv.lock new file mode 100644 index 0000000..d523ae6 --- /dev/null +++ b/python/examples/numerical-worker/uv.lock @@ -0,0 +1,7 @@ +version = 1 +requires-python = ">=3.10, <3.13" + +[[package]] +name = "numerical-worker" +version = "0.1.0" +source = { virtual = "." } diff --git a/python/examples/sc22_example.py b/python/examples/sc22_example.py index 2c47560..d4080da 100644 --- a/python/examples/sc22_example.py +++ b/python/examples/sc22_example.py @@ -30,14 +30,14 @@ def runtime_client_from_args(args: list[str]) -> Optional[RuntimeClient]: if len(args) == 0: - import sc22_worker.main + import examples.sc22_worker.main import pytket_worker.main # type: ignore from tierkreis.pyruntime import PyRuntime return PyRuntime( [ - sc22_worker.main.root, + examples.sc22_worker.main.root, pytket_worker.main.root, ] ) diff --git a/python/pyproject.toml b/python/pyproject.toml index 5f379fe..c5c03a8 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -2,7 +2,7 @@ name = "tierkreis" description = "Python client and utilities for tierkreis." readme = "README.md" -dynamic = ["version"] +version = "0.7.8" authors = [ { name = "Seyon Sivarajah", email = "seyon.sivarajah@quantinuum.com" }, { name = "Lukas Heidemann", email = "lukas.heidemann@quantinuum.com" }, @@ -42,17 +42,9 @@ build = ["build[uv]"] [tool.pytest.ini_options] markers = ["pytket"] - [build-system] -requires = ["setuptools>=65.5.0", "betterproto[compiler]==2.0.0b6"] -build-backend = "generate_protos" -backend-path = ["tierkreis/_build"] - -[tool.setuptools.dynamic] -version = { attr = "tierkreis._version.__version__" } - -[tool.setuptools.packages.find] -include = ["tierkreis*", "tierkreis/py.typed"] +requires = ["hatchling"] +build-backend = "hatchling.build" [tool.ruff] diff --git a/python/tests/__init__.py b/python/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/conftest.py b/python/tests/conftest.py index d68a33e..9ab1712 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -4,8 +4,8 @@ import pytest from grpclib.client import Channel -from test_worker import main +from tests.test_worker import main from tierkreis.builder import Namespace from tierkreis.client.runtime_client import RuntimeClient from tierkreis.client.server_client import ServerRuntime diff --git a/python/tests/controller/sample_graphdata.py b/python/tests/controller/sample_graphdata.py new file mode 100644 index 0000000..60e71e1 --- /dev/null +++ b/python/tests/controller/sample_graphdata.py @@ -0,0 +1,52 @@ +from tierkreis.controller.data.graph import ( + Const, + Func, + GraphData, + Input, + Loop, + Map, + Output, +) +from tierkreis.core import Labels + + +def loop_body() -> GraphData: + g = GraphData() + a = g.add(Input("value"))(Labels.VALUE) + one = g.add(Const(1))(Labels.VALUE) + N = g.add(Const(10))(Labels.VALUE) + + a_plus = g.add(Func("numerical-worker.iadd", {"a": a, "b": one}))(Labels.VALUE) + tag = g.add(Func("numerical-worker.igt", {"a": N, "b": a_plus}))(Labels.VALUE) + g.add(Output({"value": a_plus, "should_continue": tag})) + return g + + +def sample_graphdata() -> GraphData: + g = GraphData() + six = g.add(Const(6))(Labels.VALUE) + g_const = g.add(Const(loop_body()))(Labels.VALUE) + loop = g.add(Loop(g_const, {"value": six, "body": g_const}, "tag", Labels.VALUE)) + g.add(Output({"a": loop(Labels.VALUE)})) + return g + + +def doubler() -> GraphData: + g = GraphData() + value = g.add(Input(Labels.VALUE))(Labels.VALUE) + two = g.add(Const(2))(Labels.VALUE) + out = g.add(Func("numerical-worker.itimes", {"a": value, "b": two}))(Labels.VALUE) + g.add(Output({Labels.VALUE: out})) + return g + + +def sample_map() -> GraphData: + g = GraphData() + Ns = [g.add(Const(i))(Labels.VALUE) for i in range(10)] + const_doubler = g.add(Const(doubler()))(Labels.VALUE) + m = g.map(Map(const_doubler, Ns, {})) + folded = g.add( + Func("numerical-worker.fold_values", {str(i): v for i, v in enumerate(m)}) + ) + g.add(Output({Labels.VALUE: folded(Labels.VALUE)})) + return g diff --git a/python/tests/controller/test_models.py b/python/tests/controller/test_models.py new file mode 100644 index 0000000..7ad24d6 --- /dev/null +++ b/python/tests/controller/test_models.py @@ -0,0 +1,42 @@ +import pytest + +from tierkreis.controller.data.location import NodeLocation + +node_location_1 = NodeLocation(location=[]) +node_location_1 = node_location_1.append_node(0) +node_location_1 = node_location_1.append_loop(0) +node_location_1 = node_location_1.append_node(3) +node_location_1 = node_location_1.append_loop(2) +node_location_1 = node_location_1.append_node(0) +node_location_1 = node_location_1.append_map(8) +node_location_1 = node_location_1.append_node(0) + + +node_location_2 = NodeLocation(location=[]) +node_location_2 = node_location_2.append_node(0) +node_location_2 = node_location_2.append_loop(0) +node_location_2 = node_location_2.append_node(3) +node_location_2 = node_location_2.append_node(8) +node_location_2 = node_location_2.append_node(0) + +node_location_3 = NodeLocation(location=[]) +node_location_3 = node_location_3.append_node(0) + +node_location_4 = NodeLocation(location=[]) + + +@pytest.mark.parametrize( + ["node_location", "loc_str"], + [ + (node_location_1, "N0.L0.N3.L2.N0.M8.N0"), + (node_location_2, "N0.L0.N3.N8.N0"), + (node_location_3, "N0"), + (node_location_4, ""), + ], +) +def test_to_from_str(node_location: NodeLocation, loc_str: str): + node_location_str = str(node_location) + assert node_location_str == loc_str + + new_loc = NodeLocation.from_str(node_location_str) + assert new_loc == node_location diff --git a/python/tests/controller/test_resume.py b/python/tests/controller/test_resume.py new file mode 100644 index 0000000..f26145d --- /dev/null +++ b/python/tests/controller/test_resume.py @@ -0,0 +1,39 @@ +from pathlib import Path +from uuid import UUID + +from tests.controller.sample_graphdata import sample_graphdata, sample_map +from tierkreis.controller import run_graph +from tierkreis.controller.data.location import NodeLocation +from tierkreis.controller.executor.shell_executor import ShellExecutor +from tierkreis.controller.storage.filestorage import ControllerFileStorage +from tierkreis.core import Labels + + +def test_resume_sample_graph(): + g = sample_graphdata() + storage = ControllerFileStorage(UUID(int=0)) + executor = ShellExecutor( + Path("./python/examples/launchers"), logs_path=storage.logs_path + ) + inputs = {Labels.THUNK: g.model_dump_json().encode()} + + storage.clean_graph_files() + run_graph(storage, executor, g, inputs) + + c = storage.read_output(NodeLocation(location=[]), "a") + assert c == b"10" + + +def test_resume_sample_map(): + g = sample_map() + storage = ControllerFileStorage(UUID(int=0)) + executor = ShellExecutor( + Path("./python/examples/launchers"), logs_path=storage.logs_path + ) + inputs = {Labels.THUNK: g.model_dump_json().encode()} + + storage.clean_graph_files() + run_graph(storage, executor, g, inputs) + + c = storage.read_output(NodeLocation(location=[]), Labels.VALUE) + assert c == b"[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]" diff --git a/python/tests/sample_graph.py b/python/tests/sample_graph.py index b907953..4f14036 100644 --- a/python/tests/sample_graph.py +++ b/python/tests/sample_graph.py @@ -66,3 +66,68 @@ def sample_graph() -> TierkreisGraph: )["value"], ) return tg + + +def sample_graph_without_match() -> TierkreisGraph: + def loop_graph() -> TierkreisGraph: + ifg = TierkreisGraph() + ifg.set_outputs(value=ifg.add_tag(Labels.BREAK, value=ifg.input["x"])) + + elg = TierkreisGraph() + elg.set_outputs( + value=elg.add_tag( + Labels.CONTINUE, + value=elg.add_func( + "numerical-worker.iadd", a=elg.input["x"], b=elg.add_const(1) + ), + ) + ) + + tg = TierkreisGraph() + v1, v2 = tg.copy_value(tg.input["value"]) + tg.set_outputs( + value=tg.add_func( + "eval", + thunk=tg.add_func( + "switch", + pred=tg.add_func("numerical-worker.igt", a=v1, b=tg.add_const(5)), + if_true=tg.add_const(ifg), + if_false=tg.add_const(elg), + ), + x=v2, + )["value"] + ) + return tg + + one_graph = TierkreisGraph() + one_graph.set_outputs( + value=one_graph.add_func( + "numerical-worker.iadd", + a=one_graph.input["value"], + b=one_graph.input["other"], + ) + ) + many_graph = TierkreisGraph() + many_graph.discard(many_graph.input["other"]) + many_graph.set_outputs( + value=many_graph.add_func( + "numerical-worker.id", value=many_graph.input["value"] + ) + ) + + tg = TierkreisGraph() + tg.set_outputs( + out=tg.input["inp"], + b=tg.add_func("numerical-worker.iadd", a=tg.add_const(1), b=tg.add_const(3)), + tag=tg.add_tag("boo", value=tg.add_const("world")), + add=tg.add_func( + "numerical-worker.iadd", a=tg.add_const(23), b=tg.add_const(123) + ), + _and=tg.add_func( + "numerical-worker.and", a=tg.add_const(True), b=tg.add_const(False) + ), + loop_out=tg.add_func( + "loop", body=tg.add_const(loop_graph()), value=tg.add_const(2) + )["value"], + ) + return tg diff --git a/python/tests/test_frontend.py b/python/tests/test_frontend.py index f0a6647..7346b01 100644 --- a/python/tests/test_frontend.py +++ b/python/tests/test_frontend.py @@ -3,8 +3,8 @@ from typing import Any, Dict, List, Optional, Tuple, Type import pytest -from utils import nint_adder +from tests.utils import nint_adder from tierkreis import TierkreisGraph from tierkreis.client import RuntimeClient from tierkreis.core.graphviz import _merge_copies diff --git a/python/tests/test_generics.py b/python/tests/test_generics.py index 8f16d43..495736d 100644 --- a/python/tests/test_generics.py +++ b/python/tests/test_generics.py @@ -3,11 +3,11 @@ import pydantic as pyd import pytest -from test_worker.main import ( + +from tests.test_worker.main import ( MyGeneric, MyGenericList, ) - from tierkreis.builder import Const, Output, graph from tierkreis.client.runtime_client import RuntimeClient from tierkreis.core._internal import generic_origin diff --git a/python/tests/test_pyruntime.py b/python/tests/test_pyruntime.py index 0b8a074..01ec9a7 100644 --- a/python/tests/test_pyruntime.py +++ b/python/tests/test_pyruntime.py @@ -1,6 +1,6 @@ import pytest -from sample_graph import sample_graph as sample_g +from tests.sample_graph import sample_graph as sample_g from tierkreis.core.tierkreis_graph import TierkreisEdge, TierkreisGraph from tierkreis.core.values import TierkreisValue, VariantValue from tierkreis.pyruntime import PyRuntime diff --git a/python/tests/test_variants.py b/python/tests/test_variants.py index 6dcb182..2051494 100644 --- a/python/tests/test_variants.py +++ b/python/tests/test_variants.py @@ -7,8 +7,8 @@ import pydantic as pyd import pytest -from test_worker.main import EmptyStruct, IntStruct, MyGeneric +from tests.test_worker.main import EmptyStruct, IntStruct, MyGeneric from tierkreis.builder import Const, Output, UnionConst, graph from tierkreis.client.runtime_client import RuntimeClient from tierkreis.core.opaque_model import OpaqueModel diff --git a/python/tierkreis/_build/generate_protos.py b/python/tierkreis/_build/generate_protos.py index 08430a2..6bcef91 100644 --- a/python/tierkreis/_build/generate_protos.py +++ b/python/tierkreis/_build/generate_protos.py @@ -1,13 +1,14 @@ import glob import os +from pathlib import Path import subprocess from setuptools import build_meta as default_backend # type : ignore # PEP 517 / 518 local (aka in-tree) backend for pip/build/etc. -# We expect to be run from the project root, i.e. python/ -PROTO_INPUT_DIR = "./protos" -PROTO_OUTPUT_DIR = "./tierkreis/core/protos" +tierkreis_python_dir = Path(__file__).parent.parent.parent +PROTO_INPUT_DIR = str(tierkreis_python_dir / "protos") +PROTO_OUTPUT_DIR = str(tierkreis_python_dir / "tierkreis/core/protos") def generate_proto_code(): @@ -55,6 +56,7 @@ def build_wheel(*args, **kwargs): prepare_metadata_for_build_wheel = default_backend.prepare_metadata_for_build_wheel + # PEP 660 defines additional hooks to support "pip install --editable": def build_editable(*args, **kwargs): generate_proto_code() diff --git a/python/tierkreis/controller/__init__.py b/python/tierkreis/controller/__init__.py new file mode 100644 index 0000000..b020618 --- /dev/null +++ b/python/tierkreis/controller/__init__.py @@ -0,0 +1,47 @@ +from time import sleep + +from tierkreis.controller.data.graph import Eval, GraphData +from tierkreis.controller.data.location import NodeLocation, OutputLocation +from tierkreis.controller.executor.protocol import ControllerExecutor +from tierkreis.controller.start import NodeRunData, start, start_nodes +from tierkreis.controller.storage.protocol import ControllerStorage +from tierkreis.controller.storage.walk import walk_node +from tierkreis.core import Labels +from tierkreis.core.function import FunctionName +from tierkreis.core.tierkreis_graph import FunctionNode, PortID, TierkreisGraph + +root_loc = NodeLocation(location=[]) + + +def run_graph( + storage: ControllerStorage, + executor: ControllerExecutor, + g: GraphData, + graph_inputs: dict[str, bytes], + n_iterations: int = 10000, + polling_interval_seconds: float = 0.01, +) -> None: + for name, value in graph_inputs.items(): + storage.write_output(root_loc.append_node(-2), name, value) + + inputs: dict[PortID, OutputLocation] = { + k: (root_loc.append_node(-2), k) for k, v in graph_inputs.items() + } + # TODO: move inputs into Eval + node_run_data = NodeRunData(root_loc, Eval((0, Labels.THUNK), {}), inputs, []) + start(storage, executor, node_run_data) + resume_graph(storage, executor, n_iterations, polling_interval_seconds) + + +def resume_graph( + storage: ControllerStorage, + executor: ControllerExecutor, + n_iterations: int = 10000, + polling_interval_seconds: float = 0.01, +) -> None: + for i in range(n_iterations): + walk_results = walk_node(storage, root_loc) + start_nodes(storage, executor, walk_results.inputs_ready) + if storage.is_node_finished(root_loc): + break + sleep(polling_interval_seconds) diff --git a/python/tierkreis/controller/data/__init__.py b/python/tierkreis/controller/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tierkreis/controller/data/graph.py b/python/tierkreis/controller/data/graph.py new file mode 100644 index 0000000..73891a0 --- /dev/null +++ b/python/tierkreis/controller/data/graph.py @@ -0,0 +1,83 @@ +from dataclasses import dataclass, field +from typing import Any, Callable, Literal + +from pydantic import BaseModel + +Jsonable = Any +PortID = str +NodeIndex = int +ValueRef = tuple[NodeIndex, PortID] + + +@dataclass +class Func: + function_name: str + inputs: dict[PortID, ValueRef] + type: Literal["function"] = field(default="function") + + +@dataclass +class Eval: + graph: ValueRef + inputs: dict[PortID, ValueRef] + type: Literal["eval"] = field(default="eval") + + +@dataclass +class Loop: + body: ValueRef + inputs: dict[PortID, ValueRef] + tag_port: PortID # Used to check break or continue + output_port: PortID # Variable that is allowed to change + type: Literal["loop"] = field(default="loop") + + +@dataclass +class Map: + body: ValueRef + value_refs: list[ValueRef] + inputs: dict[PortID, ValueRef] + type: Literal["map"] = field(default="map") + + +@dataclass +class Const: + value: Jsonable + inputs: dict[PortID, ValueRef] = field(default_factory=lambda: {}) + type: Literal["const"] = field(default="const") + + +@dataclass +class Input: + name: str + inputs: dict[PortID, ValueRef] = field(default_factory=lambda: {}) + type: Literal["input"] = field(default="input") + + +@dataclass +class Output: + inputs: dict[PortID, ValueRef] + type: Literal["output"] = field(default="output") + + +NodeDef = Func | Eval | Loop | Map | Const | Input | Output + + +class GraphData(BaseModel): + nodes: list[NodeDef] = [] + outputs: list[set[PortID]] = [] + + def add(self, node: NodeDef) -> Callable[[PortID], ValueRef]: + idx = len(self.nodes) + self.nodes.append(node) + self.outputs.append(set()) + + for i, port in node.inputs.values(): + self.outputs[i].add(port) + + return lambda k: (idx, k) + + def map(self, map: Map) -> list[ValueRef]: + idx = len(self.nodes) + self.add(map) + return [(idx, str(i)) for i, _ in enumerate(map.value_refs)] diff --git a/python/tierkreis/controller/data/location.py b/python/tierkreis/controller/data/location.py new file mode 100644 index 0000000..22d0ba5 --- /dev/null +++ b/python/tierkreis/controller/data/location.py @@ -0,0 +1,116 @@ +from dataclasses import dataclass +from enum import Enum +from logging import getLogger +from pathlib import Path +from typing import Optional + +from pydantic import BaseModel +from typing_extensions import assert_never + +from tierkreis.controller.data.graph import NodeDef +from tierkreis.core.tierkreis_graph import PortID +from tierkreis.exceptions import TierkreisError + +logger = getLogger(__name__) + + +class NodeDefinition(BaseModel): + function_name: str + inputs: dict[str, Path] + outputs: dict[str, Path] + done_path: Path + logs_path: Optional[Path] + + +class NodeType(Enum): + NODE = 0 + LOOP = 1 + MAP = 2 + + def __str__(self) -> str: + match self: + case NodeType.NODE: + return "N" + case NodeType.LOOP: + return "L" + case NodeType.MAP: + return "M" + case _: + assert_never(self) + + @staticmethod + def from_str(node_type: str) -> "NodeType": + match node_type: + case "N": + return NodeType.NODE + case "L": + return NodeType.LOOP + case "M": + return NodeType.MAP + case _: + raise TierkreisError("Unknown node type.") + + +class NodeStep(BaseModel): + node_type: NodeType + idx: int + + def __str__(self) -> str: + return f"{self.node_type}{self.idx}" + + @staticmethod + def from_str(frame: str) -> "NodeStep": + try: + c, idx = frame[0], int(frame[1:]) + return NodeStep(node_type=NodeType.from_str(c), idx=idx) + except (IndexError, ValueError) as exc: + raise TierkreisError(f"Invalid NodeStep {frame}") from exc + + +class NodeLocation(BaseModel): + location: list[NodeStep] + + def append_node(self, idx: int) -> "NodeLocation": + return NodeLocation( + location=[x for x in self.location] + + [NodeStep(node_type=NodeType.NODE, idx=idx)] + ) + + def append_loop(self, idx: int) -> "NodeLocation": + return NodeLocation( + location=[x for x in self.location] + + [NodeStep(node_type=NodeType.LOOP, idx=idx)] + ) + + def append_map(self, idx: int) -> "NodeLocation": + return NodeLocation( + location=[x for x in self.location] + + [NodeStep(node_type=NodeType.MAP, idx=idx)] + ) + + def parent(self) -> "NodeLocation | None": + if not self.location: + return None + return NodeLocation(location=self.location[:-1]) + + def __str__(self) -> str: + frame_strs = [str(x) for x in self.location] + return ".".join(frame_strs) + + @staticmethod + def from_str(location: str) -> "NodeLocation": + if not location: + return NodeLocation(location=[]) + frames = location.split(".") + return NodeLocation(location=[NodeStep.from_str(x) for x in frames]) + + +OutputLocation = tuple[NodeLocation, PortID] + + +@dataclass +class NodeRunData: + node_location: NodeLocation + node: NodeDef + inputs: dict[PortID, OutputLocation] + output_list: list[PortID] diff --git a/python/tierkreis/controller/executor/__init__.py b/python/tierkreis/controller/executor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tierkreis/controller/executor/protocol.py b/python/tierkreis/controller/executor/protocol.py new file mode 100644 index 0000000..a11d47b --- /dev/null +++ b/python/tierkreis/controller/executor/protocol.py @@ -0,0 +1,6 @@ +from pathlib import Path +from typing import Protocol + + +class ControllerExecutor(Protocol): + def run(self, launcher_name: str, node_definition_path: Path) -> None: ... diff --git a/python/tierkreis/controller/executor/shell_executor.py b/python/tierkreis/controller/executor/shell_executor.py new file mode 100644 index 0000000..319701f --- /dev/null +++ b/python/tierkreis/controller/executor/shell_executor.py @@ -0,0 +1,26 @@ +import subprocess +from pathlib import Path + +from tierkreis.exceptions import TierkreisError + + +class ShellExecutor: + def __init__(self, registry_path: Path, logs_path: Path) -> None: + self.launchers_path = registry_path + self.std_err_path = logs_path + + def run(self, launcher_name: str, node_definition_path: Path) -> None: + launcher_path = self.launchers_path / launcher_name + if not launcher_path.exists(): + raise TierkreisError(f"Launcher not found: {launcher_name}.") + + if not launcher_path.is_file(): + raise TierkreisError(f"Expected launcher file. Found: {launcher_path}.") + + with open(self.std_err_path, "a") as fh: + subprocess.Popen( + [f"{self.launchers_path}/{launcher_name}", node_definition_path], + start_new_session=True, + stderr=fh, + stdout=fh, + ) diff --git a/python/tierkreis/controller/start.py b/python/tierkreis/controller/start.py new file mode 100644 index 0000000..8abc04a --- /dev/null +++ b/python/tierkreis/controller/start.py @@ -0,0 +1,144 @@ +import json +from logging import getLogger + +from pydantic import BaseModel +from typing_extensions import assert_never + +from tierkreis.controller.data.graph import Eval, Jsonable +from tierkreis.controller.data.location import NodeLocation, NodeRunData, OutputLocation +from tierkreis.controller.executor.protocol import ControllerExecutor +from tierkreis.controller.storage.protocol import ControllerStorage +from tierkreis.core import Labels +from tierkreis.core.tierkreis_graph import PortID + +logger = getLogger(__name__) + + +def start_nodes( + storage: ControllerStorage, + executor: ControllerExecutor, + node_run_data: list[NodeRunData], +) -> None: + for node_run_datum in node_run_data: + start(storage, executor, node_run_datum) + + +def start( + storage: ControllerStorage, executor: ControllerExecutor, node_run_data: NodeRunData +) -> None: + node_location = node_run_data.node_location + node = node_run_data.node + inputs = node_run_data.inputs + output_list = node_run_data.output_list + storage.write_node_definition(node_location, node.type, inputs, output_list) + + logger.debug(f"start {node_location} {node} {inputs} {output_list}") + if node.type == "function": + name = node.function_name + start_function_node(storage, executor, node_location, name, inputs, output_list) + + elif node.type == "input": + parent = node_location.parent() + if parent is None: + return + + input_loc = parent.append_node(-1) + storage.link_outputs(node_location, node.name, input_loc, node.name) + storage.mark_node_finished(node_location) + + elif node.type == "output": + storage.mark_node_finished(node_location) + + parent_loc = NodeLocation(location=node_location.location[:-1]) + pipe_inputs_to_output_location(storage, parent_loc, inputs) + storage.mark_node_finished(parent_loc) + + elif node.type == "const": + bs = bytes_from_value(node.value) + storage.write_output(node_location, Labels.VALUE, bs) + storage.mark_node_finished(node_location) + + elif node.type == "eval": + pipe_inputs_to_output_location(storage, node_location.append_node(-1), inputs) + + elif node.type == "loop": + eval_inputs = {k: v for k, v in inputs.items()} + eval_inputs["thunk"] = inputs["body"] + start( + storage, + executor, + NodeRunData( + node_location.append_loop(0), + Eval((0, Labels.THUNK), {}), # TODO: put inputs in Eval + eval_inputs, + output_list, + ), + ) + + elif node.type == "map": + pipe_inputs_to_output_location(storage, node_location.append_node(-1), inputs) + eval_inputs = {k: v for k, v in inputs.items()} + ref, port = node.body + parent = node_location.parent() + assert parent + eval_inputs["thunk"] = (parent.append_node(ref), port) + for i, value_ref in enumerate(node.value_refs): + ref, port = value_ref + eval_inputs[Labels.VALUE] = (parent.append_node(ref), port) + start( + storage, + executor, + NodeRunData( + node_location.append_map(i), + Eval((0, Labels.THUNK), {}), # TODO: put inputs in Eval + eval_inputs, + output_list, + ), + ) + + else: + assert_never(node) + + +def start_function_node( + storage: ControllerStorage, + executor: ControllerExecutor, + node_location: NodeLocation, + name: str, + inputs: dict[PortID, OutputLocation], + output_list: list[PortID], +): + launcher_name = ".".join(name.split(".")[:-1]) + name = name.split(".")[-1] + def_path = storage.write_node_definition(node_location, name, inputs, output_list) + + if name == "switch": + pred = json.loads(storage.read_output(*inputs["pred"])) + if pred: + storage.link_outputs(node_location, Labels.VALUE, *inputs["if_true"]) + else: + storage.link_outputs(node_location, Labels.VALUE, *inputs["if_false"]) + storage.mark_node_finished(node_location) + + elif name == "discard": + storage.mark_node_finished(node_location) + + else: + logger.debug(f"Executing {(str(node_location), name, inputs, output_list)}") + executor.run(launcher_name, def_path) + + +def pipe_inputs_to_output_location( + storage: ControllerStorage, + output_loc: NodeLocation, + inputs: dict[PortID, OutputLocation], +) -> None: + for new_port, (old_loc, old_port) in inputs.items(): + storage.link_outputs(output_loc, new_port, old_loc, old_port) + + +def bytes_from_value(value: Jsonable) -> bytes: + if isinstance(value, BaseModel): + return value.model_dump_json().encode() + + return json.dumps(value).encode() diff --git a/python/tierkreis/controller/storage/__init__.py b/python/tierkreis/controller/storage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tierkreis/controller/storage/filestorage.py b/python/tierkreis/controller/storage/filestorage.py new file mode 100644 index 0000000..fee2c7a --- /dev/null +++ b/python/tierkreis/controller/storage/filestorage.py @@ -0,0 +1,117 @@ +import json +import os +import shutil +from pathlib import Path +from time import time_ns +from uuid import UUID + +from tierkreis.controller.data.location import ( + NodeDefinition, + NodeLocation, + OutputLocation, +) +from tierkreis.core.tierkreis_graph import PortID + + +class ControllerFileStorage: + def __init__( + self, + workflow_id: UUID, + tierkreis_directory: Path = Path.home() / ".tierkreis" / "checkpoints", + ) -> None: + self.workflow_id = workflow_id + self.workflow_dir: Path = tierkreis_directory / str(workflow_id) + self.workflow_dir.mkdir(parents=True, exist_ok=True) + self.logs_path = self.workflow_dir / "logs" + + def _definition_path(self, node_location: NodeLocation) -> Path: + path = self.workflow_dir / str(node_location) / "definition" + path.parent.mkdir(parents=True, exist_ok=True) + return path + + def _output_path(self, node_location: NodeLocation, port_name: PortID) -> Path: + path = self.workflow_dir / str(node_location) / "outputs" / port_name + path.parent.mkdir(parents=True, exist_ok=True) + return path + + def _done_path(self, node_location: NodeLocation) -> Path: + path = self.workflow_dir / str(node_location) / "_done" + path.parent.mkdir(parents=True, exist_ok=True) + return path + + def clean_graph_files(self) -> None: + tmp_dir = Path(f"/tmp/tierkreis/archive/{self.workflow_id}/{time_ns()}") + tmp_dir.mkdir(parents=True) + if self.workflow_dir.exists(): + shutil.move(self.workflow_dir, tmp_dir) + + def add_input(self, port_name: PortID, value: bytes) -> NodeLocation: + input_loc = NodeLocation(location=[]).append_node(-1) + path = self._output_path(input_loc, port_name) + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, "wb+") as fh: + fh.write(bytes(value)) + + return input_loc + + def write_node_definition( + self, + node_location: NodeLocation, + function_name: str, + inputs: dict[PortID, OutputLocation], + output_list: list[PortID], + ) -> Path: + node_definition_path = self._definition_path(node_location) + node_definition = NodeDefinition( + function_name=function_name, + inputs={ + k: self._output_path(loc, port) for k, (loc, port) in inputs.items() + }, + outputs={k: self._output_path(node_location, k) for k in output_list}, + done_path=self._done_path(node_location), + logs_path=self.logs_path, + ) + with open(node_definition_path, "w+") as fh: + fh.write(node_definition.model_dump_json()) + + return node_definition_path + + def read_node_definition(self, node_location: NodeLocation) -> NodeDefinition: + node_definition_path = self._definition_path(node_location) + with open(node_definition_path, "r") as fh: + return NodeDefinition(**json.loads(fh.read())) + + def link_outputs( + self, + new_location: NodeLocation, + new_port: PortID, + old_location: NodeLocation, + old_port: PortID, + ) -> None: + new_dir = self._output_path(new_location, new_port) + new_dir.parent.mkdir(parents=True, exist_ok=True) + os.link(self._output_path(old_location, old_port), new_dir) + + def is_output_ready(self, node_location: NodeLocation, output_name: PortID) -> bool: + return self._output_path(node_location, output_name).exists() + + def write_output( + self, node_location: NodeLocation, output_name: PortID, value: bytes + ) -> Path: + output_path = self._output_path(node_location, output_name) + with open(output_path, "wb+") as fh: + fh.write(bytes(value)) + return output_path + + def read_output(self, node_location: NodeLocation, output_name: PortID) -> bytes: + with open(self._output_path(node_location, output_name), "rb") as fh: + return fh.read() + + def is_node_started(self, node_location: NodeLocation) -> bool: + return Path(self._definition_path(node_location)).exists() + + def is_node_finished(self, node_location: NodeLocation) -> bool: + return self._done_path(node_location).exists() + + def mark_node_finished(self, node_location: NodeLocation) -> None: + self._done_path(node_location).touch() diff --git a/python/tierkreis/controller/storage/protocol.py b/python/tierkreis/controller/storage/protocol.py new file mode 100644 index 0000000..c01c479 --- /dev/null +++ b/python/tierkreis/controller/storage/protocol.py @@ -0,0 +1,43 @@ +from pathlib import Path +from typing import Protocol + +from tierkreis.controller.data.location import ( + NodeDefinition, + NodeLocation, + OutputLocation, +) +from tierkreis.core.tierkreis_graph import PortID + + +class ControllerStorage(Protocol): + def write_node_definition( + self, + node_location: NodeLocation, + function_name: str, + inputs: dict[PortID, OutputLocation], + output_list: list[PortID], + ) -> Path: ... + + def read_node_definition(self, node_location: NodeLocation) -> NodeDefinition: ... + + def mark_node_finished(self, node_location: NodeLocation) -> None: ... + + def is_node_finished(self, node_location: NodeLocation) -> bool: ... + + def link_outputs( + self, + new_location: NodeLocation, + new_port: PortID, + old_location: NodeLocation, + old_port: PortID, + ) -> None: ... + + def write_output( + self, node_location: NodeLocation, output_name: PortID, value: bytes + ) -> Path: ... + + def read_output( + self, node_location: NodeLocation, output_name: PortID + ) -> bytes: ... + + def is_node_started(self, node_location: NodeLocation) -> bool: ... diff --git a/python/tierkreis/controller/storage/walk.py b/python/tierkreis/controller/storage/walk.py new file mode 100644 index 0000000..b52eef4 --- /dev/null +++ b/python/tierkreis/controller/storage/walk.py @@ -0,0 +1,134 @@ +import json +from dataclasses import dataclass +from logging import getLogger + +from tierkreis.controller.data.graph import Eval, GraphData +from tierkreis.controller.data.location import NodeLocation, NodeRunData +from tierkreis.controller.storage.protocol import ControllerStorage +from tierkreis.core import Labels + +logger = getLogger(__name__) + + +@dataclass +class WalkResult: + inputs_ready: list[NodeRunData] + started: list[NodeLocation] + + def extend(self, walk_result: "WalkResult") -> None: + self.inputs_ready.extend(walk_result.inputs_ready) + self.started.extend(walk_result.started) + + +def walk_node(storage: ControllerStorage, node_location: NodeLocation) -> WalkResult: + """Should only be called when a node has started and has not finished.""" + + logger.debug(f"\n\nRESUME {node_location}") + name = storage.read_node_definition(node_location).function_name + + if name == "eval": + return walk_eval(storage, node_location) + + elif name == "loop": + return walk_loop(storage, node_location) + + elif name == "map": + return walk_map(storage, node_location) + + else: + logger.debug(f"{name} already started") + return WalkResult([], [node_location]) + + +def walk_eval(storage: ControllerStorage, node_location: NodeLocation) -> WalkResult: + logger.debug("walk_eval") + walk_result = WalkResult([], []) + + message = storage.read_output(node_location.append_node(-1), Labels.THUNK) + graph = GraphData(**json.loads(message)) + + logger.debug(len(graph.nodes)) + for i, node in enumerate(graph.nodes): + new_location = node_location.append_node(i) + logger.debug(f"new_location: {new_location}") + + if storage.is_node_finished(new_location): + logger.debug(f"{new_location} is finished") + continue + + if storage.is_node_started(new_location): + logger.debug(f"{new_location} is started") + walk_result.extend(walk_node(storage, new_location)) + continue + + if all( + storage.is_node_finished(node_location.append_node(i)) + for (i, _) in node.inputs.values() + ): + logger.debug(f"{new_location} is_ready_to_start") + outputs = graph.outputs[i] + input_paths = { + k: (node_location.append_node(i), p) + for k, (i, p) in node.inputs.items() + } + node_run_data = NodeRunData(new_location, node, input_paths, list(outputs)) + walk_result.inputs_ready.append(node_run_data) + continue + + logger.debug(f"node not ready to start {new_location}") + + return walk_result + + +def walk_loop(storage: ControllerStorage, node_location: NodeLocation) -> WalkResult: + i = 0 + while storage.is_node_started(node_location.append_loop(i + 1)): + i += 1 + new_location = node_location.append_loop(i) + logger.debug(f"found latest iteration of loop: {new_location}") + + if not storage.is_node_finished(new_location): + return walk_node(storage, new_location) + + # Latest iteration is finished. Do we BREAK or CONTINUE? + should_continue = json.loads(storage.read_output(new_location, "should_continue")) + if should_continue is False: + storage.link_outputs(node_location, Labels.VALUE, new_location, Labels.VALUE) + storage.mark_node_finished(node_location) + return WalkResult([], []) + + # Include old inputs. The .value is the only one that can change. + input_paths = storage.read_node_definition(node_location).inputs + inputs = {k: (new_location.append_node(-1), v.name) for k, v in input_paths.items()} + inputs[Labels.VALUE] = (new_location, Labels.VALUE) + inputs[Labels.THUNK] = (new_location.append_node(-1), Labels.THUNK) + + node_run_data = NodeRunData( + node_location.append_loop(i + 1), + Eval((0, Labels.THUNK), {}), # TODO: put inputs in Eval + inputs, + [Labels.VALUE], + ) + return WalkResult([node_run_data], []) + + +def walk_map(storage: ControllerStorage, node_location: NodeLocation) -> WalkResult: + walk_result = WalkResult([], []) + N = 0 + all_finished = True + while storage.is_node_started(node_location.append_map(N + 1)): + N += 1 + + for i in range(N + 1): + loc = node_location.append_map(i) + if not storage.is_node_finished(loc): + all_finished = False + walk_result.extend(walk_node(storage, loc)) + + if all_finished is True: + for j in range(N + 1): + loc = node_location.append_map(j) + storage.link_outputs(node_location, str(j), loc, Labels.VALUE) + storage.mark_node_finished(node_location) + + return walk_result diff --git a/python/tierkreis/core/protos/__init__.py b/python/tierkreis/core/protos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/controller/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/controller/__init__.py new file mode 100644 index 0000000..59a3cac --- /dev/null +++ b/python/tierkreis/core/protos/tierkreis/v1alpha1/controller/__init__.py @@ -0,0 +1,256 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: v1alpha1/controller/runtime_storage_api.proto, v1alpha1/controller/tierkreis.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from datetime import datetime +from typing import ( + TYPE_CHECKING, + Dict, + List, + Optional, +) + +import betterproto +import grpclib +from betterproto.grpc.grpclib_server import ServiceBase + + +if TYPE_CHECKING: + import grpclib.server + from betterproto.grpc.grpclib_client import MetadataLike + from grpclib.metadata import Deadline + + +@dataclass(eq=False, repr=False) +class Graph(betterproto.Message): + id: str = betterproto.string_field(1) + definition: bytes = betterproto.bytes_field(101) + + +@dataclass(eq=False, repr=False) +class NodeId(betterproto.Message): + prefix: List[str] = betterproto.string_field(1) + node_index: int = betterproto.uint32_field(2) + + +@dataclass(eq=False, repr=False) +class NodeStatus(betterproto.Message): + id: "NodeId" = betterproto.message_field(1) + job_id: str = betterproto.string_field(2) + expected_duration_sec: Optional[int] = betterproto.uint32_field( + 3, optional=True, group="_expected_duration_sec" + ) + retry_count: int = betterproto.uint32_field(100) + finished_at: Optional[datetime] = betterproto.message_field( + 201, optional=True, group="_finished_at" + ) + + +@dataclass(eq=False, repr=False) +class RecordOutputRequest(betterproto.Message): + label: str = betterproto.string_field(1) + value: bytes = betterproto.bytes_field(2) + job_id: str = betterproto.string_field(101) + """Relationship fields.""" + + +@dataclass(eq=False, repr=False) +class RecordOutputResponse(betterproto.Message): + pass + + +@dataclass(eq=False, repr=False) +class RecordJobFinishedRequest(betterproto.Message): + job_id: str = betterproto.string_field(1) + error_message: Optional[str] = betterproto.string_field( + 2, optional=True, group="_error_message" + ) + + +@dataclass(eq=False, repr=False) +class RecordJobFinishedResponse(betterproto.Message): + pass + + +@dataclass(eq=False, repr=False) +class RecordNodeRunRequest(betterproto.Message): + id: "NodeId" = betterproto.message_field(1) + attempt_id: int = betterproto.uint32_field(2) + expected_duration_sec: Optional[int] = betterproto.uint32_field( + 3, optional=True, group="_expected_duration_sec" + ) + job_id: str = betterproto.string_field(101) + """Relationship fields.""" + + +@dataclass(eq=False, repr=False) +class RecordNodeRunResponse(betterproto.Message): + pass + + +@dataclass(eq=False, repr=False) +class RecordNodeFinishedRequest(betterproto.Message): + id: "NodeId" = betterproto.message_field(1) + attempt_id: int = betterproto.uint32_field(2) + outputs: bytes = betterproto.bytes_field(3) + job_id: str = betterproto.string_field(101) + """Relationship fields.""" + + +@dataclass(eq=False, repr=False) +class RecordNodeFinishedResponse(betterproto.Message): + pass + + +class CheckpointRecordingServiceStub(betterproto.ServiceStub): + async def record_output( + self, + record_output_request: "RecordOutputRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RecordOutputResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordOutput", + record_output_request, + RecordOutputResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def record_job_finished( + self, + record_job_finished_request: "RecordJobFinishedRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RecordJobFinishedResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordJobFinished", + record_job_finished_request, + RecordJobFinishedResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def record_node_run( + self, + record_node_run_request: "RecordNodeRunRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RecordNodeRunResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordNodeRun", + record_node_run_request, + RecordNodeRunResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def record_node_finished( + self, + record_node_finished_request: "RecordNodeFinishedRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RecordNodeFinishedResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordNodeFinished", + record_node_finished_request, + RecordNodeFinishedResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class CheckpointRecordingServiceBase(ServiceBase): + + async def record_output( + self, record_output_request: "RecordOutputRequest" + ) -> "RecordOutputResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def record_job_finished( + self, record_job_finished_request: "RecordJobFinishedRequest" + ) -> "RecordJobFinishedResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def record_node_run( + self, record_node_run_request: "RecordNodeRunRequest" + ) -> "RecordNodeRunResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def record_node_finished( + self, record_node_finished_request: "RecordNodeFinishedRequest" + ) -> "RecordNodeFinishedResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_record_output( + self, stream: "grpclib.server.Stream[RecordOutputRequest, RecordOutputResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.record_output(request) + await stream.send_message(response) + + async def __rpc_record_job_finished( + self, + stream: "grpclib.server.Stream[RecordJobFinishedRequest, RecordJobFinishedResponse]", + ) -> None: + request = await stream.recv_message() + response = await self.record_job_finished(request) + await stream.send_message(response) + + async def __rpc_record_node_run( + self, + stream: "grpclib.server.Stream[RecordNodeRunRequest, RecordNodeRunResponse]", + ) -> None: + request = await stream.recv_message() + response = await self.record_node_run(request) + await stream.send_message(response) + + async def __rpc_record_node_finished( + self, + stream: "grpclib.server.Stream[RecordNodeFinishedRequest, RecordNodeFinishedResponse]", + ) -> None: + request = await stream.recv_message() + response = await self.record_node_finished(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordOutput": grpclib.const.Handler( + self.__rpc_record_output, + grpclib.const.Cardinality.UNARY_UNARY, + RecordOutputRequest, + RecordOutputResponse, + ), + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordJobFinished": grpclib.const.Handler( + self.__rpc_record_job_finished, + grpclib.const.Cardinality.UNARY_UNARY, + RecordJobFinishedRequest, + RecordJobFinishedResponse, + ), + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordNodeRun": grpclib.const.Handler( + self.__rpc_record_node_run, + grpclib.const.Cardinality.UNARY_UNARY, + RecordNodeRunRequest, + RecordNodeRunResponse, + ), + "/tierkreis.v1alpha1.controller.CheckpointRecordingService/RecordNodeFinished": grpclib.const.Handler( + self.__rpc_record_node_finished, + grpclib.const.Cardinality.UNARY_UNARY, + RecordNodeFinishedRequest, + RecordNodeFinishedResponse, + ), + } diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/graph/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/graph/__init__.py new file mode 100644 index 0000000..9ea774a --- /dev/null +++ b/python/tierkreis/core/protos/tierkreis/v1alpha1/graph/__init__.py @@ -0,0 +1,542 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: v1alpha1/graph.proto +# plugin: python-betterproto +# This file has been @generated +import builtins +from dataclasses import dataclass +from typing import ( + Dict, + List, + Optional, +) + +import betterproto + + +@dataclass(eq=False, repr=False) +class Location(betterproto.Message): + """Structured name identifying a path from a root to a Runtime""" + + location: List[str] = betterproto.string_field(1) + """ + Atoms in the name, each identifying a Runtime that is a child of the + previous Runtime + """ + + +@dataclass(eq=False, repr=False) +class StructValue(betterproto.Message): + """A `Value::struct` of unordered fields named by distinct strings""" + + map: Dict[str, "Value"] = betterproto.map_field( + 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + """Fields keyed by name""" + + +@dataclass(eq=False, repr=False) +class PairValue(betterproto.Message): + """ + Contents of a `Value::pair` or one entry in a `MapValue`, i.e. two values + """ + + first: "Value" = betterproto.message_field(1) + """First element of the `Value::pair` or key of the map entry""" + + second: "Value" = betterproto.message_field(2) + """Second element of the `Value::pair` or value/RHS of the map entry""" + + +@dataclass(eq=False, repr=False) +class MapValue(betterproto.Message): + """Contents of a `Value::map`: a collection of (key, value) pairs""" + + pairs: List["PairValue"] = betterproto.message_field(1) + """ + List of mappings. (Note: keys of a protobuf `map` cannot be message types + like `Value`.) + """ + + +@dataclass(eq=False, repr=False) +class VecValue(betterproto.Message): + """Contents of a `Value::vec`: a list of values""" + + vec: List["Value"] = betterproto.message_field(2) + """Elements of the list""" + + +@dataclass(eq=False, repr=False) +class VariantValue(betterproto.Message): + """ + A `Value` tagged with a string to make a disjoint union of component types + """ + + tag: str = betterproto.string_field(1) + """ + Label, selects the appropriate handler of a `Node::match` destructuring the + variant + """ + + value: "Value" = betterproto.message_field(2) + """Contents""" + + +@dataclass(eq=False, repr=False) +class Value(betterproto.Message): + """A value that can be passed around a Tierkreis graph.""" + + graph: "Graph" = betterproto.message_field(1, group="value") + """A Tierkreis Graph (i.e., a higher-order function value)""" + + integer: int = betterproto.int64_field(2, group="value") + """Signed 64-bit integer value""" + + boolean: bool = betterproto.bool_field(3, group="value") + """Boolean value""" + + str: builtins.str = betterproto.string_field(9, group="value") + """String value""" + + flt: float = betterproto.double_field(10, group="value") + """Double-precision (64-bit) floating-point value""" + + pair: "PairValue" = betterproto.message_field(4, group="value") + """A pair of two other values""" + + vec: "VecValue" = betterproto.message_field(5, group="value") + """An arbitrary-length list of values""" + + map: "MapValue" = betterproto.message_field(7, group="value") + """ + A map from keys (values) to values. Keys must be hashable, i.e. must not be + or contain `graph`s, `map`s, `struct`s or `float`s + """ + + struct: "StructValue" = betterproto.message_field(8, group="value") + """ + A structure (aka record) value with string-named fields (each storing a + `Value`) + """ + + variant: "VariantValue" = betterproto.message_field(12, group="value") + """ + A value tagged with a string to make a value of sum type (see + `Type::Variant`) + """ + + +@dataclass(eq=False, repr=False) +class Output(betterproto.Message): + """ + A value was placed onto an edge - used for visualization by the Python + runtime. + """ + + edge: "Edge" = betterproto.message_field(1) + """The edge (of the outermost graph) onto which the value was placed""" + + value: "Value" = betterproto.message_field(2) + """The value, i.e. an intermediate during graph execution""" + + +@dataclass(eq=False, repr=False) +class OutputStream(betterproto.Message): + """ + A list of all the `Output`s, used for visualization by the Python runtime. + """ + + stream: List["Output"] = betterproto.message_field(1) + """All the `Output`s so far.""" + + +@dataclass(eq=False, repr=False) +class Type(betterproto.Message): + """ + / A type of values (generally of `Kind::Star`) that can be passed around a + Tierkreis graph (except `Row`, of `Kind::Row`, describing a row of values + which is not a value itself). + """ + + var: str = betterproto.string_field(1, group="type") + """ + Type variable, used inside polymorphic `TypeScheme`s only. Can be of + [Kind::Row] or [Kind::Star]. + """ + + int: "Empty" = betterproto.message_field(2, group="type") + """Type of signed integers (values must be `Value::int`)""" + + bool: "Empty" = betterproto.message_field(3, group="type") + """Type of booleans (values must be `Value::bool`)""" + + graph: "GraphType" = betterproto.message_field(4, group="type") + """Type of `Value::graph`s, with a specified row of inputs and outputs""" + + pair: "PairType" = betterproto.message_field(5, group="type") + """Type of `Value::pair`s, with types for the two elements""" + + vec: "Type" = betterproto.message_field(6, group="type") + """ + Type of arbitrary-length homogeneous lists, with the type for all elements. + Values must be `Value::vec`. + """ + + row: "RowType" = betterproto.message_field(7, group="type") + """ + An unordered row of named types. Unlike the other variants (except possibly + `Type::Var`), this is a `Kind::Row`, not a type of values (`Kind::Star`), + so cannot be used as a member of any other Type (e.g. `Type::Vec`). + However, this can appear in a `LacksConstraint` or `PartitionConstraint`, + or in a `tierkreis.v1alpha.signature.UnifyError`. + """ + + map: "PairType" = betterproto.message_field(9, group="type") + """ + Type of maps, containing two types - one for all the keys and one for the + values. We do nothing to rule out key types that are not hashable, only the + actual `Value`s used as keys. + """ + + struct: "StructType" = betterproto.message_field(10, group="type") + """ + Type of `Value::struct`s with a specified row of field names and types. + Optionally, the struct type itself may have a name. + """ + + str: "Empty" = betterproto.message_field(11, group="type") + """Type of strings (values must be `Value::str`)""" + + flt: "Empty" = betterproto.message_field(12, group="type") + """Type of double-precision floats ()""" + + variant: "RowType" = betterproto.message_field(14, group="type") + """ + A disjoint (tagged) union of other types, given as a row. May be open, for + the output of a `Tag` operation, or closed, for the input to match (where + the handlers are known). + """ + + +@dataclass(eq=False, repr=False) +class GraphType(betterproto.Message): + """ + Describes the type of a graph by specifying the input and output rows + """ + + inputs: "RowType" = betterproto.message_field(1) + """The inputs to the graph (known and/or variable)""" + + outputs: "RowType" = betterproto.message_field(2) + """The outputs to the graph (known and/or variable)""" + + +@dataclass(eq=False, repr=False) +class PairType(betterproto.Message): + """Used to describe the type of a pair, or of a map (key + value)""" + + first: "Type" = betterproto.message_field(1) + """Type of the first element of the pair, or the keys in the map""" + + second: "Type" = betterproto.message_field(2) + """Type of the second element of the pair, or the values in the map""" + + +@dataclass(eq=False, repr=False) +class RowType(betterproto.Message): + """ + An unordered set of names (strings), each with a type; possibly plus a + variable (of `Kind::Row`) to stand for an unknown portion. + """ + + content: Dict[str, "Type"] = betterproto.map_field( + 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + """ + Known labels, and types for each (of course these may contain variables) + """ + + rest: str = betterproto.string_field(2) + """ + May contain the name of a variable (of `Kind::Row`), in which case the + `RowType` is an "open row" and also includes any number of other fields + stood for by that variable (with names disjoint from those in `content`). + Otherwise, a "closed row" i.e. exactly/only the fields in `content` + """ + + +@dataclass(eq=False, repr=False) +class StructType(betterproto.Message): + """ + Describes a `Type::Struct`: the row of fields, optionally with a name for + the type. + """ + + shape: "RowType" = betterproto.message_field(1) + """The fields in the struct""" + + name: str = betterproto.string_field(2, group="name_opt") + """Optional name of the struct type. (TODO how is this used?)""" + + +@dataclass(eq=False, repr=False) +class FunctionName(betterproto.Message): + """Qualified name of a function""" + + namespaces: List[str] = betterproto.string_field(1) + """ + Identifies a `tierkreis.v1alpha1.signature.Namespace` (perhaps a subspace) + by traversing downwards from the root + """ + + name: str = betterproto.string_field(2) + """Name of the function within that subspace""" + + +@dataclass(eq=False, repr=False) +class FunctionNode(betterproto.Message): + """ + Describes a `Node::function`, identifying the function to call, and + optionally a time after which execution should be retried + """ + + name: "FunctionName" = betterproto.message_field(1) + """Qualified name of the function""" + + retry_secs: Optional[int] = betterproto.uint32_field( + 2, optional=True, group="_retry_secs" + ) + """ + Time after which execution of the function may be assumed to have failed + and should be retried (in due course) + """ + + +@dataclass(eq=False, repr=False) +class BoxNode(betterproto.Message): + """Describes a `Node::box`: a location at which to run a graph""" + + loc: "Location" = betterproto.message_field(1) + """On which runtime to run the subgraph""" + + graph: "Graph" = betterproto.message_field(2) + """Definition of the graph to run""" + + +@dataclass(eq=False, repr=False) +class Node(betterproto.Message): + """A node in a `Graph`""" + + input: "Empty" = betterproto.message_field(1, group="node") + """The node that emits a graph's input values.""" + + output: "Empty" = betterproto.message_field(2, group="node") + """The node that receives a graph's output values.""" + + const: "Value" = betterproto.message_field(3, group="node") + """A node that emits a constant value (contained within).""" + + box: "BoxNode" = betterproto.message_field(4, group="node") + """ + A subgraph embedded as a single node. The ports of the node are the ports + of the embedded graph. Box nodes can be used to conveniently compose common + subgraphs. The box also specifies a location at which to run the graph. + """ + + function: "FunctionNode" = betterproto.message_field(5, group="node") + """ + A node that executes a function with a specified name. The type and runtime + behavior of a function node depend on the functions provided by the + environment in which the graph is interpreted. + """ + + match: "Empty" = betterproto.message_field(6, group="node") + """ + Perform pattern matching on a variant value, with handlers according to the + input `Type::variant` + """ + + tag: str = betterproto.string_field(7, group="node") + """Create a variant. Tag(tag) :: forall T. T -> Variant(tag:T | ...)""" + + +@dataclass(eq=False, repr=False) +class Edge(betterproto.Message): + """An edge in a `Graph`""" + + port_from: str = betterproto.string_field(1) + """Source (out-)port of `node_from`""" + + port_to: str = betterproto.string_field(2) + """Target/destination (in-)port of `node_to`""" + + node_from: int = betterproto.uint32_field(3) + """Source node""" + + node_to: int = betterproto.uint32_field(4) + """Source port""" + + edge_type: "Type" = betterproto.message_field(5) + """ + Explicit annotation of the type of the edge. Client may (optionally) + provide; typechecking will (always) fill in. + """ + + +@dataclass(eq=False, repr=False) +class Graph(betterproto.Message): + """ + A computation graph is a directed acyclic port graph in which data flows + along the edges and as it is processed by the nodes. Nodes in the graph + are densely numbered starting from 0. The inputs of the graph are emitted + by a `Node::input` at index 0, and the outputs are received by a + `Node::output` at index 1. Each node has labelled input and output ports, + encoded implicitly as the endpoints of the graph's edges. The port labels + are unique among a node's input and output ports individually, but input + and output ports with the same label are considered different. Any port in + the graph has exactly one edge connected to it. + """ + + nodes: List["Node"] = betterproto.message_field(1) + """ + The nodes in the graph. The first two must be a `Node::input` and a + `Node::output`, and there must not be any other such nodes at any other + index. + """ + + edges: List["Edge"] = betterproto.message_field(2) + """ + The edges in the graph. Each can optionally be annotated with a type. + """ + + name: str = betterproto.string_field(3) + """ + User-provided name of the graph. Used for debug/display, does not affect + execution. + """ + + input_order: List[str] = betterproto.string_field(4) + """ + Optional ordering of input ports used by graph builders if available. + Listed ports will be reported and wired up in the order given and before + ports that are not listed + """ + + output_order: List[str] = betterproto.string_field(5) + """Like `input_order` but for the output ports.""" + + +@dataclass(eq=False, repr=False) +class Empty(betterproto.Message): + """ + This is supposed to be `google.protobuf.Empty` but unfortunately there is + no support for this in `betterproto` yet. + """ + + pass + + +@dataclass(eq=False, repr=False) +class TypeScheme(betterproto.Message): + """ + A polymorphic type scheme. Usually (but not necessarily) for a function. + """ + + variables: List["TypeSchemeVar"] = betterproto.message_field(1) + """ + Variables over which the scheme is polymorphic, each with its `Kind`. A + concrete type (usable for a port or edge) can be obtained by giving a type + or row (according to the `Kind`) for each. + """ + + constraints: List["Constraint"] = betterproto.message_field(2) + """ + Constraints restricting the legal instantiations of the [Self::variables] + """ + + body: "Type" = betterproto.message_field(3) + """ + The body of the type scheme, i.e. potentially containing occurrences of the + `variables`, into which values (`Type`s or `RowTypes`) for the variables + are substituted in order to instantiate the scheme into a concrete type. + """ + + +@dataclass(eq=False, repr=False) +class TypeSchemeVar(betterproto.Message): + """Describes a variable bound by a `TypeScheme`""" + + name: str = betterproto.string_field(1) + """Name of the variable""" + + kind: "Kind" = betterproto.message_field(2) + """Whether the variable is a `Type` of `Value`s or a `RowType`""" + + +@dataclass(eq=False, repr=False) +class Constraint(betterproto.Message): + """ + Specifies a restriction on possible instantiations of type variables in a + [TypeScheme] + """ + + lacks: "LacksConstraint" = betterproto.message_field(1, group="constraint") + """A type variable of `Kind::Row` does *not* have a specified label""" + + partition: "PartitionConstraint" = betterproto.message_field(2, group="constraint") + """ + A row is the union of two other rows (we expect at least two of the three + rows to contain variables) + """ + + +@dataclass(eq=False, repr=False) +class PartitionConstraint(betterproto.Message): + """ + `Constraint` that a row is the union of two other rows (which for any label + in common, must have `Type`s that can be made the same) + """ + + left: "Type" = betterproto.message_field(1) + """ + One input to the union - a `Type::Row` or a `Type::Var` of `Kind::Row` + """ + + right: "Type" = betterproto.message_field(2) + """ + The other input to the union - a `Type::Row` or a `Type::Var` of + `Kind::Row` + """ + + union: "Type" = betterproto.message_field(3) + """ + The result, i.e. `left` and `right` merged together. Could be a `Type::Row` + or a `Type::Var` of `Kind::Row` + """ + + +@dataclass(eq=False, repr=False) +class LacksConstraint(betterproto.Message): + """ + `Constraint` that a row does not contain an element with the specified name + """ + + row: "Type" = betterproto.message_field(1) + """A `Type::row` or a `Type::var` of `Kind::row`""" + + label: str = betterproto.string_field(2) + """Field name that must not be present in `row`""" + + +@dataclass(eq=False, repr=False) +class Kind(betterproto.Message): + """ + The kind of a type variable - i.e. whether the "type" variable stands for a + single type, or (some part of) a row. + """ + + star: "Empty" = betterproto.message_field(1, group="kind") + """Kind of types (describing sets of values)""" + + row: "Empty" = betterproto.message_field(2, group="kind") + """Kind of rows (unordered names each with a type)""" diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/jobs/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/jobs/__init__.py new file mode 100644 index 0000000..fd3e5dd --- /dev/null +++ b/python/tierkreis/core/protos/tierkreis/v1alpha1/jobs/__init__.py @@ -0,0 +1,291 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: v1alpha1/jobs.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Dict, + List, + Optional, +) + +import betterproto +import grpclib +from betterproto.grpc.grpclib_server import ServiceBase + +from .. import ( + graph as _graph__, + signature as _signature__, +) + + +if TYPE_CHECKING: + import grpclib.server + from betterproto.grpc.grpclib_client import MetadataLike + from grpclib.metadata import Deadline + + +@dataclass(eq=False, repr=False) +class JobHandle(betterproto.Message): + uuid: str = betterproto.string_field(1) + attempt_id: int = betterproto.uint32_field(2) + + +@dataclass(eq=False, repr=False) +class StartJobRequest(betterproto.Message): + graph: "_graph__.Graph" = betterproto.message_field(1) + inputs: "_graph__.StructValue" = betterproto.message_field(2) + job_handle: "JobHandle" = betterproto.message_field(3) + + +@dataclass(eq=False, repr=False) +class StartJobResponse(betterproto.Message): + job_handle: "JobHandle" = betterproto.message_field(1, group="result") + type_errors: "_signature__.TypeErrors" = betterproto.message_field( + 2, group="result" + ) + runtime_error: str = betterproto.string_field(3, group="result") + + +@dataclass(eq=False, repr=False) +class RunningJobsRequest(betterproto.Message): + pass + + +@dataclass(eq=False, repr=False) +class RunningJobsResponse(betterproto.Message): + jobs: List["JobStatus"] = betterproto.message_field(1) + + +@dataclass(eq=False, repr=False) +class StopJobRequest(betterproto.Message): + job_id: str = betterproto.string_field(1) + + +@dataclass(eq=False, repr=False) +class StopJobResponse(betterproto.Message): + pass + + +@dataclass(eq=False, repr=False) +class JobStatusRequest(betterproto.Message): + handle: "JobHandle" = betterproto.message_field(1) + + +@dataclass(eq=False, repr=False) +class JobStatusResponse(betterproto.Message): + status: "JobStatus" = betterproto.message_field(1) + + +@dataclass(eq=False, repr=False) +class Empty(betterproto.Message): + """ + This is supposed to be `google.protobuf.Empty` but unfortunately there is + no support for this in `betterproto` yet. + """ + + pass + + +@dataclass(eq=False, repr=False) +class JobStatus(betterproto.Message): + handle: "JobHandle" = betterproto.message_field(1) + success: "Empty" = betterproto.message_field(2, group="status") + running: "Empty" = betterproto.message_field(3, group="status") + error: str = betterproto.string_field(4, group="status") + + +@dataclass(eq=False, repr=False) +class DeleteCompletedRequest(betterproto.Message): + pass + + +@dataclass(eq=False, repr=False) +class DeleteCompletedResponse(betterproto.Message): + job_handles: List["JobHandle"] = betterproto.message_field(1) + """handles of deleted jobs""" + + +class JobControlStub(betterproto.ServiceStub): + async def running_jobs( + self, + running_jobs_request: "RunningJobsRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RunningJobsResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.jobs.JobControl/RunningJobs", + running_jobs_request, + RunningJobsResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def start_job( + self, + start_job_request: "StartJobRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "StartJobResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.jobs.JobControl/StartJob", + start_job_request, + StartJobResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def job_status( + self, + job_status_request: "JobStatusRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "JobStatusResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.jobs.JobControl/JobStatus", + job_status_request, + JobStatusResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def stop_job( + self, + stop_job_request: "StopJobRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "StopJobResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.jobs.JobControl/StopJob", + stop_job_request, + StopJobResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def delete_completed( + self, + delete_completed_request: "DeleteCompletedRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "DeleteCompletedResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.jobs.JobControl/DeleteCompleted", + delete_completed_request, + DeleteCompletedResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class JobControlBase(ServiceBase): + + async def running_jobs( + self, running_jobs_request: "RunningJobsRequest" + ) -> "RunningJobsResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def start_job( + self, start_job_request: "StartJobRequest" + ) -> "StartJobResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def job_status( + self, job_status_request: "JobStatusRequest" + ) -> "JobStatusResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def stop_job(self, stop_job_request: "StopJobRequest") -> "StopJobResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def delete_completed( + self, delete_completed_request: "DeleteCompletedRequest" + ) -> "DeleteCompletedResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_running_jobs( + self, stream: "grpclib.server.Stream[RunningJobsRequest, RunningJobsResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.running_jobs(request) + await stream.send_message(response) + + async def __rpc_start_job( + self, stream: "grpclib.server.Stream[StartJobRequest, StartJobResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.start_job(request) + await stream.send_message(response) + + async def __rpc_job_status( + self, stream: "grpclib.server.Stream[JobStatusRequest, JobStatusResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.job_status(request) + await stream.send_message(response) + + async def __rpc_stop_job( + self, stream: "grpclib.server.Stream[StopJobRequest, StopJobResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.stop_job(request) + await stream.send_message(response) + + async def __rpc_delete_completed( + self, + stream: "grpclib.server.Stream[DeleteCompletedRequest, DeleteCompletedResponse]", + ) -> None: + request = await stream.recv_message() + response = await self.delete_completed(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/tierkreis.v1alpha1.jobs.JobControl/RunningJobs": grpclib.const.Handler( + self.__rpc_running_jobs, + grpclib.const.Cardinality.UNARY_UNARY, + RunningJobsRequest, + RunningJobsResponse, + ), + "/tierkreis.v1alpha1.jobs.JobControl/StartJob": grpclib.const.Handler( + self.__rpc_start_job, + grpclib.const.Cardinality.UNARY_UNARY, + StartJobRequest, + StartJobResponse, + ), + "/tierkreis.v1alpha1.jobs.JobControl/JobStatus": grpclib.const.Handler( + self.__rpc_job_status, + grpclib.const.Cardinality.UNARY_UNARY, + JobStatusRequest, + JobStatusResponse, + ), + "/tierkreis.v1alpha1.jobs.JobControl/StopJob": grpclib.const.Handler( + self.__rpc_stop_job, + grpclib.const.Cardinality.UNARY_UNARY, + StopJobRequest, + StopJobResponse, + ), + "/tierkreis.v1alpha1.jobs.JobControl/DeleteCompleted": grpclib.const.Handler( + self.__rpc_delete_completed, + grpclib.const.Cardinality.UNARY_UNARY, + DeleteCompletedRequest, + DeleteCompletedResponse, + ), + } diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/runtime/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/runtime/__init__.py new file mode 100644 index 0000000..fd7773d --- /dev/null +++ b/python/tierkreis/core/protos/tierkreis/v1alpha1/runtime/__init__.py @@ -0,0 +1,140 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: v1alpha1/runtime.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Dict, + Optional, +) + +import betterproto +import grpclib +from betterproto.grpc.grpclib_server import ServiceBase + +from .. import ( + graph as _graph__, + signature as _signature__, +) + + +if TYPE_CHECKING: + import grpclib.server + from betterproto.grpc.grpclib_client import MetadataLike + from grpclib.metadata import Deadline + + +@dataclass(eq=False, repr=False) +class Callback(betterproto.Message): + """ + A target to which to send `Runtime::RunGraph` or + `tierkreis.v1alpha1.worker.Worker.RunFunction` callback requests + """ + + uri: str = betterproto.string_field(1) + """Connection point - host and address""" + + loc: "_graph__.Location" = betterproto.message_field(2) + """ + location to pass as `RunGraphRequest.loc` or + `tierkreis.v1alpha1.worker.RunFunctionRequest.loc` + """ + + +@dataclass(eq=False, repr=False) +class RunGraphRequest(betterproto.Message): + """`Runtime.RunGraph` request to run a graph with inputs""" + + graph: "_graph__.Graph" = betterproto.message_field(1) + """Graph to run""" + + inputs: "_graph__.StructValue" = betterproto.message_field(2) + """Inputs to pass to the graph""" + + type_check: bool = betterproto.bool_field(3) + """ + Hint as to whether the runtime should type-check the graph (against the + inputs) before executing any nodes. (If absent, defaults to false.) + """ + + loc: "_graph__.Location" = betterproto.message_field(4) + """ + Location (a child of this Runtime, or a path down a forwarding chain to a + parent Runtime) + """ + + escape: "Callback" = betterproto.message_field(5) + """ + Optional - may contain a target to which + `tierkreis.v1alpha1.worker.Worker.RunFunction` requests should be sent for + any function nodes in the graph that use functions not known by this + (recipient) Runtime. + """ + + +@dataclass(eq=False, repr=False) +class RunGraphResponse(betterproto.Message): + """Result of a `Runtime.RunGraph` request""" + + success: "_graph__.StructValue" = betterproto.message_field(1, group="result") + """ + Graph ran to completion and produced the given outputs (a string-to-Value + map) + """ + + error: str = betterproto.string_field(2, group="result") + """A runtime error during execution of the Graph""" + + type_errors: "_signature__.TypeErrors" = betterproto.message_field( + 3, group="result" + ) + """ + The Graph failed type-checking before execution started (only if + `RunGraphRequest.type_check` was true.) + """ + + +class RuntimeStub(betterproto.ServiceStub): + async def run_graph( + self, + run_graph_request: "RunGraphRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RunGraphResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.runtime.Runtime/RunGraph", + run_graph_request, + RunGraphResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class RuntimeBase(ServiceBase): + + async def run_graph( + self, run_graph_request: "RunGraphRequest" + ) -> "RunGraphResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_run_graph( + self, stream: "grpclib.server.Stream[RunGraphRequest, RunGraphResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.run_graph(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/tierkreis.v1alpha1.runtime.Runtime/RunGraph": grpclib.const.Handler( + self.__rpc_run_graph, + grpclib.const.Cardinality.UNARY_UNARY, + RunGraphRequest, + RunGraphResponse, + ), + } diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/signature/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/signature/__init__.py new file mode 100644 index 0000000..8c1acaa --- /dev/null +++ b/python/tierkreis/core/protos/tierkreis/v1alpha1/signature/__init__.py @@ -0,0 +1,467 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: v1alpha1/signature.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Dict, + List, + Optional, +) + +import betterproto +import grpclib +from betterproto.grpc.grpclib_server import ServiceBase + +from .. import graph as _graph__ + + +if TYPE_CHECKING: + import grpclib.server + from betterproto.grpc.grpclib_client import MetadataLike + from grpclib.metadata import Deadline + + +@dataclass(eq=False, repr=False) +class FunctionDeclaration(betterproto.Message): + """ + Information about a function: its polymorphic [TypeScheme], user-readable + description, and port ordering (for debug/display purposes, not used by the + typechecker). + """ + + type_scheme: "_graph__.TypeScheme" = betterproto.message_field(2) + """/ Polymorphic type scheme describing all possible input/output types""" + + description: str = betterproto.string_field(3) + """Human-readable documentation""" + + input_order: List[str] = betterproto.string_field(4) + """Order in which to display input ports""" + + output_order: List[str] = betterproto.string_field(5) + """Order in which to display output ports""" + + +@dataclass(eq=False, repr=False) +class ListFunctionsRequest(betterproto.Message): + """ + Request to list the functions known to a Runtime, optionally filtering by a + Location + """ + + loc: "_graph__.Location" = betterproto.message_field(1) + """ + Filter to only report functions available in the specified location or + children thereof. (The default/empty location means the root, i.e. all + functions.) + """ + + +@dataclass(eq=False, repr=False) +class ListFunctionsResponse(betterproto.Message): + """ + aka the "Signature" of a Runtime: the `FunctionDeclaration`s and descendant + `Location`s that it knows. + """ + + root: "Namespace" = betterproto.message_field(1) + """ + Every function the runtime can run, and the locations in which it can run + each + """ + + aliases: Dict[str, "_graph__.TypeScheme"] = betterproto.map_field( + 2, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + """Named aliases for polymorphic types""" + + scopes: List["_graph__.Location"] = betterproto.message_field(3) + """All the locations the Runtime knows.""" + + +@dataclass(eq=False, repr=False) +class InferTypeRequest(betterproto.Message): + """ + Request to infer the type of a graph (used for `TypeInference::InferType`) + """ + + value: "_graph__.Value" = betterproto.message_field(1) + """Value whose type (scheme) to infer""" + + loc: "_graph__.Location" = betterproto.message_field(2) + """ + Sub-location of the runtime in to which to check. If this leads to + `ErrorVariant::unknown_function` errors that do not occur without the + location, this indicates usage of functions available only outside that + location i.e. these would use an escape hatch if the Graph were run. + """ + + +@dataclass(eq=False, repr=False) +class InferTypeResponse(betterproto.Message): + """ + Result of inferring the type of a value via `TypeInference::InferType` + """ + + success: "InferTypeSuccess" = betterproto.message_field(1, group="response") + """ + Inference succeeded, return the type-annotated value and inferred type + (scheme) + """ + + error: "TypeErrors" = betterproto.message_field(2, group="response") + """Type inference failed because of one or more type errors""" + + +@dataclass(eq=False, repr=False) +class InferTypeSuccess(betterproto.Message): + """A type successfully inferred by `TypeInference::InferType`""" + + value: "_graph__.Value" = betterproto.message_field(1) + """ + The value whose type was inferred. The same as the value passed in, except + that any `Value::graph`s within will have their edges annotated with the + inferred types (`Edge::edge_type`). + """ + + type_scheme: "_graph__.TypeScheme" = betterproto.message_field(2) + """ + Type scheme inferred for the value, i.e. explicitly listing any type + variables over which the value is polymorphic. (E.g. if the value is an + empty list.) + """ + + +@dataclass(eq=False, repr=False) +class GraphWithInputs(betterproto.Message): + """ + A graph with (optionally) input values for it. Used for + `InferGraphTypesRequest`s and `InferGraphTypesResponse`. + """ + + graph: "_graph__.Graph" = betterproto.message_field(1) + """The graph""" + + inputs: Optional["_graph__.StructValue"] = betterproto.message_field( + 2, optional=True, group="_inputs" + ) + """Optionally, input values to feed to the graph""" + + +@dataclass(eq=False, repr=False) +class InferGraphTypesRequest(betterproto.Message): + """ + Used by `tierkreis-typecheck` Rust(PYO3)/python interop library to request + type inference of a graph with input values. + """ + + gwi: "GraphWithInputs" = betterproto.message_field(1) + """Graph and inputs whose types to infer""" + + functions: "Namespace" = betterproto.message_field(2) + """The signature of functions to check against.""" + + +@dataclass(eq=False, repr=False) +class InferGraphTypesResponse(betterproto.Message): + """ + Used by `tierkreis-typecheck` Rust(PYO3)/python interop library to report + results of type inference on a graph with input values. + """ + + success: "GraphWithInputs" = betterproto.message_field(1, group="response") + """Inference was successful, return the type""" + + error: "TypeErrors" = betterproto.message_field(2, group="response") + """Inference failed due to one or more `TierkreisTypeError`s""" + + +@dataclass(eq=False, repr=False) +class Empty(betterproto.Message): + """ + This is supposed to be `google.protobuf.Empty` but unfortunately there is + no support for this in `betterproto` yet. + """ + + pass + + +@dataclass(eq=False, repr=False) +class GraphLocation(betterproto.Message): + """ + A series of these identifies where in a `Value::graph` or `GraphWithInputs` + was the cause of a `TierkreisTypeError`. + """ + + vec_index: int = betterproto.uint32_field(2, group="location") + """ + Where the previous location(s) identify a `Value::vec`, identifies one + element by index + """ + + node_idx: int = betterproto.uint32_field(4, group="location") + """ + Where the previous location(s) identify a graph, the error is in the + indexed node. (If-and-only-if the node is a Box or Const, may be followed + by more `GraphLocation`s.) + """ + + edge: "_graph__.Edge" = betterproto.message_field(5, group="location") + """ + Where the previous location(s) identify a graph, the error is on the + indicated edge. (Will end the sequence of `GraphLocation`s.) + """ + + input: "Empty" = betterproto.message_field(6, group="location") + """ + Where previous location(s) identify a graph, the error is in the input node + """ + + output: "Empty" = betterproto.message_field(7, group="location") + """ + Where previous location(s) identify a graph, the error is in the output + node + """ + + struct_field: str = betterproto.string_field(8, group="location") + """ + Where the previous location(s) identify a struct value (e.g. inside a + Const), the error is in the named field of that struct + """ + + pair_first: "Empty" = betterproto.message_field(9, group="location") + """ + Where the previous location(s) identify a `Value::pair`, the error is in + the first element + """ + + pair_second: "Empty" = betterproto.message_field(10, group="location") + """ + Where the previous location(s) identify a `Value::pair`, the error is in + the second element + """ + + map_key: "Empty" = betterproto.message_field(11, group="location") + """ + Where the previous location(s) identify a `Value::map`, the error is in one + of the keys (does not specify which) + """ + + map_value: "Empty" = betterproto.message_field(12, group="location") + """ + Where the previous location(s) identify a `Value::map`, the error is in one + of the values (does not specify which) + """ + + input_value: str = betterproto.string_field(13, group="location") + """ + For `TypeInference::InferType`, indicates the error is in one of the + `GraphWithInputs::inputs` + """ + + +@dataclass(eq=False, repr=False) +class TypeErrors(betterproto.Message): + """A Collection of `TypeError`s""" + + errors: List["TierkreisTypeError"] = betterproto.message_field(1) + """List of errors""" + + +@dataclass(eq=False, repr=False) +class UnifyError(betterproto.Message): + """`ErrorVariant` that two types failed to unify.""" + + expected: "_graph__.Type" = betterproto.message_field(1) + """The type that was expected.""" + + found: "_graph__.Type" = betterproto.message_field(2) + """The type that was actually inferred.""" + + +@dataclass(eq=False, repr=False) +class TypeVarError(betterproto.Message): + """ + `ErrorVariant` that a type scheme is ill-formed because it refers to an + unknown type variable. + """ + + variable: "_graph__.TypeSchemeVar" = betterproto.message_field(1) + """The unknown type variable.""" + + type_scheme: "_graph__.TypeScheme" = betterproto.message_field(2) + """The ill-formed type scheme.""" + + +@dataclass(eq=False, repr=False) +class ErrorVariant(betterproto.Message): + """Errors that can occur during type checking.""" + + unify: "UnifyError" = betterproto.message_field(1, group="error") + """Two types failed to unify.""" + + kind: str = betterproto.string_field(2, group="error") + """A type scheme is ill-formed due to a kind mismatch.""" + + unknown_function: "_graph__.FunctionName" = betterproto.message_field( + 3, group="error" + ) + """A graph referred to an unknown function.""" + + unknown_type_var: "TypeVarError" = betterproto.message_field(4, group="error") + """ + A type scheme is ill-formed because it refers to an unknown type variable. + """ + + bound: str = betterproto.string_field(5, group="error") + """ + A type constraint (`LacksConstraint` or `PartitionConstraint`) is + unsatisfiable. + """ + + +@dataclass(eq=False, repr=False) +class TierkreisTypeError(betterproto.Message): + """ + An error preventing type inference in a graph passed to + `TypeInference::InferType` or `tierkreis.v1alpha1.runtime.Runime.RunGraph` + """ + + variant: "ErrorVariant" = betterproto.message_field(1) + """Detail of the error""" + + location: List["GraphLocation"] = betterproto.message_field(2) + """ + Identifies where in the value/graph the error occurred. Locations go from + outermost to innermost in nested const/box graphs. + """ + + +@dataclass(eq=False, repr=False) +class NamespaceItem(betterproto.Message): + """ + A `FunctionDeclaration` with a set of `Location`s at which the function is + supported. + """ + + decl: "FunctionDeclaration" = betterproto.message_field(1) + """ + Declaration of the function, including typescheme; identical at all + `locations` + """ + + locations: List["_graph__.Location"] = betterproto.message_field(2) + """The locations (aka scopes) at which the function is supported""" + + +@dataclass(eq=False, repr=False) +class Namespace(betterproto.Message): + """ + Tree-structured mapping (sharing common prefixes) from + `tierkreis.v1alpha1.graph.FunctionName`s to `NamespaceItem`s + """ + + functions: Dict[str, "NamespaceItem"] = betterproto.map_field( + 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + """`NamespaceItem`s at this level of the qualified-name hierarchy""" + + subspaces: Dict[str, "Namespace"] = betterproto.map_field( + 2, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + """ + Mappings for subtrees of the name hierarchy, i.e. for qualnames with a + longer prefix (the map key being the next atom of prefix) + """ + + +class SignatureStub(betterproto.ServiceStub): + async def list_functions( + self, + list_functions_request: "ListFunctionsRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "ListFunctionsResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.signature.Signature/ListFunctions", + list_functions_request, + ListFunctionsResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class TypeInferenceStub(betterproto.ServiceStub): + async def infer_type( + self, + infer_type_request: "InferTypeRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "InferTypeResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.signature.TypeInference/InferType", + infer_type_request, + InferTypeResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class SignatureBase(ServiceBase): + + async def list_functions( + self, list_functions_request: "ListFunctionsRequest" + ) -> "ListFunctionsResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_list_functions( + self, + stream: "grpclib.server.Stream[ListFunctionsRequest, ListFunctionsResponse]", + ) -> None: + request = await stream.recv_message() + response = await self.list_functions(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/tierkreis.v1alpha1.signature.Signature/ListFunctions": grpclib.const.Handler( + self.__rpc_list_functions, + grpclib.const.Cardinality.UNARY_UNARY, + ListFunctionsRequest, + ListFunctionsResponse, + ), + } + + +class TypeInferenceBase(ServiceBase): + + async def infer_type( + self, infer_type_request: "InferTypeRequest" + ) -> "InferTypeResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_infer_type( + self, stream: "grpclib.server.Stream[InferTypeRequest, InferTypeResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.infer_type(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/tierkreis.v1alpha1.signature.TypeInference/InferType": grpclib.const.Handler( + self.__rpc_infer_type, + grpclib.const.Cardinality.UNARY_UNARY, + InferTypeRequest, + InferTypeResponse, + ), + } diff --git a/python/tierkreis/core/protos/tierkreis/v1alpha1/worker/__init__.py b/python/tierkreis/core/protos/tierkreis/v1alpha1/worker/__init__.py new file mode 100644 index 0000000..bf2b28c --- /dev/null +++ b/python/tierkreis/core/protos/tierkreis/v1alpha1/worker/__init__.py @@ -0,0 +1,107 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: v1alpha1/worker.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Dict, + Optional, +) + +import betterproto +import grpclib +from betterproto.grpc.grpclib_server import ServiceBase + +from .. import ( + graph as _graph__, + runtime as _runtime__, +) + + +if TYPE_CHECKING: + import grpclib.server + from betterproto.grpc.grpclib_client import MetadataLike + from grpclib.metadata import Deadline + + +@dataclass(eq=False, repr=False) +class RunFunctionRequest(betterproto.Message): + """Request for `Worker::RunFunction` to run a named function""" + + function: "_graph__.FunctionName" = betterproto.message_field(1) + """Name of the function to run""" + + inputs: "_graph__.StructValue" = betterproto.message_field(2) + """Inputs to pass to the function""" + + loc: "_graph__.Location" = betterproto.message_field(3) + """ + Identifies a child location/Worker to run the function (or identifies a + forwarding chain for `run_function` requests from a + `tierkreis.v1alpha1.runtime.RunGraphRequest.escape` back up to a parent). + If absent, function can be run at any child location supporting it. + """ + + callback: "_runtime__.Callback" = betterproto.message_field(4) + """ + Allows code executing the function to call + `tierkreis.v1alpha1.runtime.Runtime.RunGraph` on the root node where the + user originally `RunGraph`d, perhaps via forwarding. + """ + + +@dataclass(eq=False, repr=False) +class RunFunctionResponse(betterproto.Message): + """ + Result of `Worker::RunFunction` (success case - errors are reported as GRPC + errors) + """ + + outputs: "_graph__.StructValue" = betterproto.message_field(1) + """Result values named by port""" + + +class WorkerStub(betterproto.ServiceStub): + async def run_function( + self, + run_function_request: "RunFunctionRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "RunFunctionResponse": + return await self._unary_unary( + "/tierkreis.v1alpha1.worker.Worker/RunFunction", + run_function_request, + RunFunctionResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class WorkerBase(ServiceBase): + + async def run_function( + self, run_function_request: "RunFunctionRequest" + ) -> "RunFunctionResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_run_function( + self, stream: "grpclib.server.Stream[RunFunctionRequest, RunFunctionResponse]" + ) -> None: + request = await stream.recv_message() + response = await self.run_function(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/tierkreis.v1alpha1.worker.Worker/RunFunction": grpclib.const.Handler( + self.__rpc_run_function, + grpclib.const.Cardinality.UNARY_UNARY, + RunFunctionRequest, + RunFunctionResponse, + ), + } diff --git a/python/tierkreis/core/tierkreis_graph.py b/python/tierkreis/core/tierkreis_graph.py index 06d214c..9e4a79f 100644 --- a/python/tierkreis/core/tierkreis_graph.py +++ b/python/tierkreis/core/tierkreis_graph.py @@ -21,6 +21,7 @@ import betterproto import networkx as nx +from networkx.classes.multidigraph import MultiDiGraph import tierkreis.core.protos.tierkreis.v1alpha1.graph as pg from tierkreis.core.function import FunctionName @@ -302,7 +303,7 @@ class MissingEdge(Exception): def __init__(self, name: str = "") -> None: """Create an empty graph with an optional name.""" self.name = name - self._graph = nx.MultiDiGraph() + self._graph = MultiDiGraph() inp = self.add_node(InputNode()) assert inp.idx == self.input_node_idx output = self.add_node(OutputNode()) @@ -483,7 +484,7 @@ def inline_boxes(self, recursive=False) -> "TierkreisGraph": incoming_ports = {edge.target.port: edge.source for edge in curr_inputs} curr_outputs = [ graph._to_tkedge(e) - for e in _to_edgedata(graph._graph.out_edges(node_idx, keys=True)) + for e in _to_edgedata(graph._graph.out_edges(node_idx, keys=True)) # type: ignore ] # Removal all incident edges graph._graph.remove_edges_from( @@ -513,7 +514,7 @@ def nodes(self) -> Iterator[TierkreisNode]: def edges(self) -> Iterator[TierkreisEdge]: """Iterator over all edges in the graph.""" - return map(self._to_tkedge, _to_edgedata(self._graph.edges(keys=True))) + return map(self._to_tkedge, _to_edgedata(self._graph.edges(keys=True))) # type: ignore def __getitem__(self, key: Union[int, NodeRef]) -> TierkreisNode: name = key.idx if isinstance(key, NodeRef) else key @@ -600,11 +601,11 @@ def annotate_input( """Annotate an input port of the graph with a type.""" (in_edge,) = [ e - for e in _to_edgedata(self._graph.out_edges(self.input_node_idx, keys=True)) + for e in _to_edgedata(self._graph.out_edges(self.input_node_idx, keys=True)) # type: ignore if e[2][0] == input_port ] tk_type = _to_tierkreis_type(edge_type) - self._graph.edges[in_edge]["type"] = tk_type + self._graph.edges[in_edge]["type"] = tk_type # type: ignore def annotate_output( self, output_port: str, edge_type: Optional[Union[Type, TierkreisType]] @@ -612,12 +613,12 @@ def annotate_output( """Annotate an output port of the graph with a type.""" (out_edge,) = [ e - for e in _to_edgedata(self._graph.in_edges(self.output_node_idx, keys=True)) + for e in _to_edgedata(self._graph.in_edges(self.output_node_idx, keys=True)) # type: ignore if e[2][1] == output_port ] tk_type = _to_tierkreis_type(edge_type) - self._graph.edges[out_edge]["type"] = tk_type + self._graph.edges[out_edge]["type"] = tk_type # type: ignore def get_edge(self, source: NodePort, target: NodePort) -> TierkreisEdge: """Retrieve an edge from the graph by source and target ports. @@ -652,7 +653,8 @@ def in_edges(self, node: Union[NodeRef, int]) -> Iterator[TierkreisEdge]: """Iterator over incoming edges to a node.""" node_name = node if isinstance(node, int) else node.idx return map( - self._to_tkedge, _to_edgedata(self._graph.in_edges(node_name, keys=True)) + self._to_tkedge, + _to_edgedata(self._graph.in_edges(node_name, keys=True)), # type: ignore ) def out_edges(self, node: Union[NodeRef, int]) -> Iterator[TierkreisEdge]: @@ -660,7 +662,7 @@ def out_edges(self, node: Union[NodeRef, int]) -> Iterator[TierkreisEdge]: node_idx = node if isinstance(node, int) else node.idx return map( self._to_tkedge, - _to_edgedata(self._graph.out_edges(node_idx, keys=True)), + _to_edgedata(self._graph.out_edges(node_idx, keys=True)), # type: ignore ) def discard(self, out_port: NodePort) -> None: diff --git a/python/tierkreis/exceptions.py b/python/tierkreis/exceptions.py new file mode 100644 index 0000000..2e1c835 --- /dev/null +++ b/python/tierkreis/exceptions.py @@ -0,0 +1,2 @@ +class TierkreisError(Exception): + """An error thrown in the Tierkreis library.""" diff --git a/tierkreis-core/src/type_checker/mod.rs b/tierkreis-core/src/type_checker/mod.rs index 5bc6789..10dfdca 100644 --- a/tierkreis-core/src/type_checker/mod.rs +++ b/tierkreis-core/src/type_checker/mod.rs @@ -570,7 +570,7 @@ struct Internalize<'a, 'b> { rigid: bool, } -impl<'a, 'b> Internalize<'a, 'b> { +impl Internalize<'_, '_> { fn get_variable(&mut self, var: TypeVar, kind: Kind) -> Result { match self.variables.get(&var) { Some((_, actual_kind)) if *actual_kind != kind => Err(InternalizeError::Kind), diff --git a/tierkreis-core/src/type_checker/solve.rs b/tierkreis-core/src/type_checker/solve.rs index 0df86d7..752a31e 100644 --- a/tierkreis-core/src/type_checker/solve.rs +++ b/tierkreis-core/src/type_checker/solve.rs @@ -890,7 +890,7 @@ struct Ancestors<'a> { visited: HashSet, } -impl<'a> Iterator for Ancestors<'a> { +impl Iterator for Ancestors<'_> { type Item = TypeId; fn next(&mut self) -> Option { diff --git a/tierkreis-core/src/type_checker/visit.rs b/tierkreis-core/src/type_checker/visit.rs index d2c1202..fcc767f 100644 --- a/tierkreis-core/src/type_checker/visit.rs +++ b/tierkreis-core/src/type_checker/visit.rs @@ -17,7 +17,7 @@ pub(super) struct Visitor<'a> { pub variables: HashMap, } -impl<'a> Visitor<'a> { +impl Visitor<'_> { pub fn visit_graph_with_inputs( &mut self, gwi: &GraphWithInputs, diff --git a/tierkreis-runtime/src/workers/local.rs b/tierkreis-runtime/src/workers/local.rs index 6ebb8b6..e53a499 100644 --- a/tierkreis-runtime/src/workers/local.rs +++ b/tierkreis-runtime/src/workers/local.rs @@ -16,7 +16,6 @@ pub struct LocalWorker { impl LocalWorker { /// Creates a new instance with no functions (yet). See [add_function](Self::add_function) - pub fn new() -> Self { Self { signature: Default::default(), diff --git a/tierkreis_visualization/.gitignore b/tierkreis_visualization/.gitignore new file mode 100644 index 0000000..412bf43 --- /dev/null +++ b/tierkreis_visualization/.gitignore @@ -0,0 +1,11 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info +.idea + +# Virtual environments +.venv diff --git a/tierkreis_visualization/README.md b/tierkreis_visualization/README.md new file mode 100644 index 0000000..a7eeca8 --- /dev/null +++ b/tierkreis_visualization/README.md @@ -0,0 +1,9 @@ +# Tierkreis visualizer + +WIP browser based visualizer. + +``` +uv run fastapi dev tierkreis_visualization/main.py +``` + +for the development server. diff --git a/tierkreis_visualization/pyproject.toml b/tierkreis_visualization/pyproject.toml new file mode 100644 index 0000000..65459dc --- /dev/null +++ b/tierkreis_visualization/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "tiekreis_visualization" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "fastapi[standard]>=0.115.11", + "jinja2>=3.1.6", + "pydantic-settings>=2.8.1", + "tierkreis", +] + +[tool.uv.sources] +tierkreis = { path = "../python", editable = true } diff --git a/tierkreis_visualization/templates/eval.html b/tierkreis_visualization/templates/eval.html new file mode 100644 index 0000000..3c1c72d --- /dev/null +++ b/tierkreis_visualization/templates/eval.html @@ -0,0 +1,54 @@ +{% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %} + + + + Tierkreis visualization: EVAL node + + + + + {{ breadcrumbs_macro(breadcrumbs) }} +
+ +

Greetings, one and all!

+
+ +
+
+ + + + + \ No newline at end of file diff --git a/tierkreis_visualization/templates/fallback.html b/tierkreis_visualization/templates/fallback.html new file mode 100644 index 0000000..51e9c81 --- /dev/null +++ b/tierkreis_visualization/templates/fallback.html @@ -0,0 +1,48 @@ +{% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %} + + + + Tierkreis visualization: node + + + {{ breadcrumbs_macro(breadcrumbs) }} + + +

{{ definition.function_name }}

+ +
    +
  • + Inputs +
      + {% for input,path in definition.inputs.items() %} +
    • {{ input }} ({{ path }})
    • + {% endfor %} +
    +
  • + +
  • + Outputs +
      + {% for output,path in definition.outputs.items() %} +
    • {{ output }} ({{ path }})
    • + {% endfor %} +
    +
  • +
  • logs
  • +
+ + + + \ No newline at end of file diff --git a/tierkreis_visualization/templates/index.html b/tierkreis_visualization/templates/index.html new file mode 100644 index 0000000..455eb4c --- /dev/null +++ b/tierkreis_visualization/templates/index.html @@ -0,0 +1,8 @@ + + + Tierkreis visualization + + + All running workflows + + \ No newline at end of file diff --git a/tierkreis_visualization/templates/loop.html b/tierkreis_visualization/templates/loop.html new file mode 100644 index 0000000..8c244bf --- /dev/null +++ b/tierkreis_visualization/templates/loop.html @@ -0,0 +1,40 @@ +{% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %} + + + + Tierkreis visualization: LOOP node + + + + + {{ breadcrumbs_macro(breadcrumbs) }} +
+ + + + \ No newline at end of file diff --git a/tierkreis_visualization/templates/macros/breadcrumbs.html b/tierkreis_visualization/templates/macros/breadcrumbs.html new file mode 100644 index 0000000..2b0a7ce --- /dev/null +++ b/tierkreis_visualization/templates/macros/breadcrumbs.html @@ -0,0 +1,5 @@ +{% macro breadcrumbs_macro(links) -%} + {% for link in links %} + {{ link[0] }} + {% endfor %} +{%- endmacro %} diff --git a/tierkreis_visualization/templates/workflows.html b/tierkreis_visualization/templates/workflows.html new file mode 100644 index 0000000..4c421b4 --- /dev/null +++ b/tierkreis_visualization/templates/workflows.html @@ -0,0 +1,13 @@ + + + Tierkreis visualization: workflows + + +

Workflows

+ + + \ No newline at end of file diff --git a/tierkreis_visualization/tierkreis_visualization/__init__.py b/tierkreis_visualization/tierkreis_visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tierkreis_visualization/tierkreis_visualization/config.py b/tierkreis_visualization/tierkreis_visualization/config.py new file mode 100644 index 0000000..b3a2821 --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/config.py @@ -0,0 +1,15 @@ +from pathlib import Path + +from pydantic_settings import BaseSettings +from starlette.templating import Jinja2Templates + + +class Settings(BaseSettings): + tierkreis_path: Path = Path.home() / ".tierkreis" / "checkpoints" + + +CONFIG = Settings() + +assert CONFIG.tierkreis_path.exists() + +templates = Jinja2Templates(directory="templates") diff --git a/tierkreis_visualization/tierkreis_visualization/data/eval.py b/tierkreis_visualization/tierkreis_visualization/data/eval.py new file mode 100644 index 0000000..dabf11d --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/data/eval.py @@ -0,0 +1,62 @@ +import json +from typing import Optional, assert_never + +from pydantic import BaseModel +from tierkreis.controller.data.location import NodeDefinition, NodeLocation +from tierkreis.controller.data.graph import GraphData +from tierkreis.controller.storage.protocol import ControllerStorage +from tierkreis.core import Labels + +from tierkreis_visualization.data.models import PyNode, NodeStatus, PyEdge + + +class EvalNodeData(BaseModel): + nodes: list[PyNode] + edges: list[PyEdge] + + +def node_status(is_finished: bool, definition: Optional[NodeDefinition]) -> NodeStatus: + if is_finished: + return NodeStatus.FINISHED + + if definition is not None: + return NodeStatus.STARTED + + return NodeStatus.NOT_STARTED + + +def get_eval_node( + storage: ControllerStorage, node_location: NodeLocation +) -> EvalNodeData: + thunk = storage.read_output(node_location.append_node(-1), Labels.THUNK) + graph = GraphData(**json.loads(thunk)) + + pynodes: list[PyNode] = [] + for i, node in enumerate(graph.nodes): + new_location = node_location.append_node(i) + is_finished = storage.is_node_finished(new_location) + try: + definition = storage.read_node_definition(new_location) + except FileNotFoundError: + definition = None + + status = node_status(is_finished, definition) + + match node.type: + case "function": + name = node.function_name + case "const" | "map" | "eval" | "input" | "output" | "loop": + name = node.type + case _: + assert_never(node) + + pynode = PyNode(id=i, status=status, function_name=name) + pynodes.append(pynode) + + py_edges: list[PyEdge] = [] + for idx, node in enumerate(graph.nodes): + for p0, (i, p1) in node.inputs.items(): + py_edge = PyEdge(from_node=i, from_port=p1, to_node=idx, to_port=p0) + py_edges.append(py_edge) + + return EvalNodeData(nodes=pynodes, edges=py_edges) diff --git a/tierkreis_visualization/tierkreis_visualization/data/loop.py b/tierkreis_visualization/tierkreis_visualization/data/loop.py new file mode 100644 index 0000000..1e29925 --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/data/loop.py @@ -0,0 +1,39 @@ +from pydantic import BaseModel +from tierkreis.controller.data.location import NodeLocation +from tierkreis.controller.storage.protocol import ControllerStorage +from tierkreis.core import Labels + +from tierkreis_visualization.data.models import PyNode, PyEdge, NodeStatus + + +class LoopNodeData(BaseModel): + nodes: list[PyNode] + edges: list[PyEdge] + + +def get_loop_node( + storage: ControllerStorage, node_location: NodeLocation +) -> LoopNodeData: + i = 0 + while storage.is_node_started(node_location.append_loop(i + 1)): + i += 1 + new_location = node_location.append_loop(i) + + nodes = [ + PyNode(id=n, status=NodeStatus.FINISHED, function_name=f"L{n}") + for n in range(i) + ] + + last_status = ( + NodeStatus.FINISHED + if storage.is_node_finished(new_location) + else NodeStatus.STARTED + ) + nodes.append(PyNode(id=i, status=last_status, function_name=f"L{i}")) + + edges = [ + PyEdge(from_node=n, from_port=Labels.VALUE, to_node=n + 1, to_port=Labels.VALUE) + for n in range(i) + ] + + return LoopNodeData(nodes=nodes, edges=edges) diff --git a/tierkreis_visualization/tierkreis_visualization/data/models.py b/tierkreis_visualization/tierkreis_visualization/data/models.py new file mode 100644 index 0000000..82b92b6 --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/data/models.py @@ -0,0 +1,22 @@ +from enum import StrEnum + +from pydantic import BaseModel + + +class NodeStatus(StrEnum): + NOT_STARTED = "Not started" + STARTED = "Started" + FINISHED = "Finished" + + +class PyNode(BaseModel): + id: int + status: NodeStatus + function_name: str + + +class PyEdge(BaseModel): + from_node: int + from_port: str + to_node: int + to_port: str diff --git a/tierkreis_visualization/tierkreis_visualization/data/workflows.py b/tierkreis_visualization/tierkreis_visualization/data/workflows.py new file mode 100644 index 0000000..98aadcb --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/data/workflows.py @@ -0,0 +1,17 @@ +import os +from uuid import UUID + +from tierkreis_visualization.config import CONFIG + + +def get_workflows(): + folders = os.listdir(CONFIG.tierkreis_path) + workflow_ids: list[UUID] = [] + for folder in folders: + try: + workflow_id = UUID(folder) + workflow_ids.append(workflow_id) + except (TypeError, ValueError): + continue + + return workflow_ids diff --git a/tierkreis_visualization/tierkreis_visualization/main.py b/tierkreis_visualization/tierkreis_visualization/main.py new file mode 100644 index 0000000..7a72334 --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/main.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI, Request + +from tierkreis_visualization.config import templates +from tierkreis_visualization.routers.workflows import router as workflows_router + +app = FastAPI() + +app.include_router(workflows_router) + + +@app.get("/") +def read_root(request: Request): + return templates.TemplateResponse(request=request, name="index.html") diff --git a/tierkreis_visualization/tierkreis_visualization/routers/__init__.py b/tierkreis_visualization/tierkreis_visualization/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tierkreis_visualization/tierkreis_visualization/routers/models.py b/tierkreis_visualization/tierkreis_visualization/routers/models.py new file mode 100644 index 0000000..3c9bbf2 --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/routers/models.py @@ -0,0 +1,67 @@ +from typing import assert_never + +from pydantic import BaseModel, Field + +from tierkreis_visualization.data.models import PyNode, NodeStatus, PyEdge + + +def color_from_status(status: NodeStatus) -> str: + match status: + case NodeStatus.NOT_STARTED: + return "#D3D3D3" + case NodeStatus.STARTED: + return "#00FF00" + case NodeStatus.FINISHED: + return "#42f5ec" + case _: + assert_never(status) + + +class JSNode(BaseModel): + id: int + title: str + label: str + shape: str + color: str + + @staticmethod + def from_pynode(pynode: PyNode): + title = f"Function name: {pynode.function_name}\nStatus: {pynode.status}" + + return JSNode( + id=pynode.id, + title=title, + label=pynode.function_name, + color=color_from_status(pynode.status), + shape="box", + ) + + +class JSEdge(BaseModel): + from_node: int = Field(..., serialization_alias="from") + to: int + title: str + label: str + color: str + arrows: str = "to" + + @staticmethod + def from_py_edge(py_edge: PyEdge): + return JSEdge( + from_node=py_edge.from_node, + to=py_edge.to_node, + title=f"{py_edge.from_port}->{py_edge.to_port}", + color="black", + label=py_edge.to_port, + ) + + +class JSGraph(BaseModel): + nodes: list[JSNode] + edges: list[JSEdge] + + @staticmethod + def from_python(nodes: list[PyNode], edges: list[PyEdge]): + js_nodes = [JSNode.from_pynode(x) for x in nodes] + js_edges = [JSEdge.from_py_edge(x) for x in edges] + return JSGraph(nodes=js_nodes, edges=js_edges) diff --git a/tierkreis_visualization/tierkreis_visualization/routers/navigation.py b/tierkreis_visualization/tierkreis_visualization/routers/navigation.py new file mode 100644 index 0000000..8ef4d34 --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/routers/navigation.py @@ -0,0 +1,21 @@ +from uuid import UUID + +from tierkreis.controller.data.location import NodeLocation + + +def breadcrumb_links(crumbs: list[str]) -> list[tuple[str, str]]: + url_path = "" + links: list[tuple[str, str]] = [] + for crumb in crumbs: + url_path = url_path + crumb + links.append((crumb, url_path)) + + return links + + +def breadcrumbs( + workflow_id: UUID, node_location: NodeLocation +) -> list[tuple[str, str]]: + node_location_strs: list[str] = [f".{x}" for x in node_location.location] + static_links = ["/workflows", f"/{workflow_id}/nodes/-"] + return breadcrumb_links(static_links + node_location_strs) diff --git a/tierkreis_visualization/tierkreis_visualization/routers/workflows.py b/tierkreis_visualization/tierkreis_visualization/routers/workflows.py new file mode 100644 index 0000000..da3d8ba --- /dev/null +++ b/tierkreis_visualization/tierkreis_visualization/routers/workflows.py @@ -0,0 +1,113 @@ +import json +from typing import Any +from uuid import UUID + +from fastapi import APIRouter, Request +from pydantic import BaseModel +from starlette.responses import JSONResponse, PlainTextResponse +from tierkreis.controller.data.location import NodeLocation, NodeDefinition +from tierkreis.controller.storage.filestorage import ControllerFileStorage +from tierkreis.controller.storage.protocol import ControllerStorage + +from tierkreis_visualization.config import CONFIG, templates +from tierkreis_visualization.data.eval import get_eval_node +from tierkreis_visualization.data.loop import get_loop_node +from tierkreis_visualization.data.workflows import get_workflows +from tierkreis_visualization.routers.models import JSGraph +from tierkreis_visualization.routers.navigation import breadcrumbs + +router = APIRouter(prefix="/workflows") + + +@router.get("/") +def list_workflows(request: Request): + workflows = get_workflows() + return templates.TemplateResponse( + request=request, name="workflows.html", context={"workflows": workflows} + ) + + +class NodeResponse(BaseModel): + definition: NodeDefinition + + +def parse_node_location(node_location_str: str) -> NodeLocation: + if node_location_str.startswith("-"): + node_location_str = node_location_str[1:] + if node_location_str.startswith("."): + node_location_str = node_location_str[1:] + + return NodeLocation.from_str(node_location_str) + + +def get_storage(workflow_id: UUID) -> ControllerStorage: + return ControllerFileStorage( + workflow_id=workflow_id, tierkreis_directory=CONFIG.tierkreis_path + ) + + +@router.get("/{workflow_id}/nodes/{node_location_str}") +def get_node(request: Request, workflow_id: UUID, node_location_str: str): + storage = get_storage(workflow_id) + + node_location = parse_node_location(node_location_str) + definition = storage.read_node_definition(node_location) + function_name = definition.function_name + + if function_name == "eval": + data = get_eval_node(storage, node_location) + name = "eval.html" + ctx: dict[str, Any] = JSGraph.from_python(data.nodes, data.edges).model_dump( + by_alias=True + ) + + elif function_name == "loop": + data = get_loop_node(storage, node_location) + name = "loop.html" + ctx = JSGraph.from_python(data.nodes, data.edges).model_dump(by_alias=True) + + else: + name = "fallback.html" + ctx = {"definition": definition.model_dump()} + + ctx["breadcrumbs"] = breadcrumbs(workflow_id, node_location) + + return templates.TemplateResponse(request=request, name=name, context=ctx) + + +@router.get("/{workflow_id}/nodes/{node_location_str}/inputs/{port_name}") +def get_input(workflow_id: UUID, node_location_str: str, port_name: str): + node_location = parse_node_location(node_location_str) + storage = get_storage(workflow_id) + definition = storage.read_node_definition(node_location) + + with open(definition.inputs[port_name], "rb") as fh: + return JSONResponse(json.loads(fh.read())) + + +@router.get("/{workflow_id}/nodes/{node_location_str}/outputs/{port_name}") +def get_output(workflow_id: UUID, node_location_str: str, port_name: str): + node_location = parse_node_location(node_location_str) + storage = get_storage(workflow_id) + definition = storage.read_node_definition(node_location) + + with open(definition.outputs[port_name], "rb") as fh: + return JSONResponse(json.loads(fh.read())) + + +@router.get("/{workflow_id}/nodes/{node_location_str}/logs") +def get_logs(workflow_id: UUID, node_location_str: str): + node_location = parse_node_location(node_location_str) + storage = get_storage(workflow_id) + definition = storage.read_node_definition(node_location) + + if definition.logs_path is None: + return PlainTextResponse("Node definition not found.") + + messages = "" + + with open(definition.logs_path, "rb") as fh: + for line in fh: + messages += line.decode() + + return PlainTextResponse(messages) diff --git a/tierkreis_visualization/uv.lock b/tierkreis_visualization/uv.lock new file mode 100644 index 0000000..6becba1 --- /dev/null +++ b/tierkreis_visualization/uv.lock @@ -0,0 +1,945 @@ +version = 1 +revision = 1 +requires-python = ">=3.12" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "betterproto" +version = "2.0.0b6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpclib" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/43/4c44efd75f2ef48a16b458c2fe2cff7aa74bab8fcadf2653bb5110a87f97/betterproto-2.0.0b6.tar.gz", hash = "sha256:720ae92697000f6fcf049c69267d957f0871654c8b0d7458906607685daee784", size = 63775 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/6d/007abe4bf14cd02a3c07710c22f0e3252b55b06678de2535ae5f03b7433a/betterproto-2.0.0b6-py3-none-any.whl", hash = "sha256:a0839ec165d110a69d0d116f4d0e2bec8d186af4db826257931f0831dab73fcf", size = 64275 }, +] + +[package.optional-dependencies] +compiler = [ + { name = "black" }, + { name = "isort" }, + { name = "jinja2" }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 }, +] + +[[package]] +name = "fastapi" +version = "0.115.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/28/c5d26e5860df807241909a961a37d45e10533acef95fc368066c7dd186cd/fastapi-0.115.11.tar.gz", hash = "sha256:cc81f03f688678b92600a65a5e618b93592c65005db37157147204d8924bf94f", size = 294441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/5d/4d8bbb94f0dbc22732350c06965e40740f4a92ca560e90bb566f4f73af41/fastapi-0.115.11-py3-none-any.whl", hash = "sha256:32e1541b7b74602e4ef4a0260ecaf3aadf9d4f19590bba3e1bf2ac4666aa2c64", size = 94926 }, +] + +[package.optional-dependencies] +standard = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705 }, +] + +[package.optional-dependencies] +standard = [ + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "graphviz" +version = "0.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/83/5a40d19b8347f017e417710907f824915fba411a9befd092e52746b63e9f/graphviz-0.20.3.zip", hash = "sha256:09d6bc81e6a9fa392e7ba52135a9d49f1ed62526f96499325930e87ca1b5925d", size = 256455 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/be/d59db2d1d52697c6adc9eacaf50e8965b6345cc143f671e1ed068818d5cf/graphviz-0.20.3-py3-none-any.whl", hash = "sha256:81f848f2904515d8cd359cc611faba817598d2feaac4027b266aa3eda7b3dde5", size = 47126 }, +] + +[[package]] +name = "grpclib" +version = "0.4.8rc2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h2" }, + { name = "multidict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/42/abb76d3557bee73c4e2ad18ba8b85af1ee5bb177a419b2f2d0bb2ea52d80/grpclib-0.4.8rc2.tar.gz", hash = "sha256:46dfd0b39f548e3d0881e82bc691ee3a9eaca98170d1715f8db7fd1d6c0d0652", size = 62791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/7e/9528c2e06c4277e1a9da5d47755618a3460e40d80bf94642d4f39fe59c6a/grpclib-0.4.8rc2-py3-none-any.whl", hash = "sha256:d7ddef43b9ac214ec52770f298f2c244786e038899a73dfe03943995306650bb", size = 76358 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "multidict" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/4a/7874ca44a1c9b23796c767dd94159f6c17e31c0e7d090552a1c623247d82/multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8", size = 71066 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/e2/0153a8db878aef9b2397be81e62cbc3b32ca9b94e0f700b103027db9d506/multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b", size = 49204 }, + { url = "https://files.pythonhosted.org/packages/bb/9d/5ccb3224a976d1286f360bb4e89e67b7cdfb87336257fc99be3c17f565d7/multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4", size = 29807 }, + { url = "https://files.pythonhosted.org/packages/62/32/ef20037f51b84b074a89bab5af46d4565381c3f825fc7cbfc19c1ee156be/multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44", size = 30000 }, + { url = "https://files.pythonhosted.org/packages/97/81/b0a7560bfc3ec72606232cd7e60159e09b9cf29e66014d770c1315868fa2/multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd", size = 131820 }, + { url = "https://files.pythonhosted.org/packages/49/3b/768bfc0e41179fbccd3a22925329a11755b7fdd53bec66dbf6b8772f0bce/multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e", size = 136272 }, + { url = "https://files.pythonhosted.org/packages/71/ac/fd2be3fe98ff54e7739448f771ba730d42036de0870737db9ae34bb8efe9/multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c", size = 135233 }, + { url = "https://files.pythonhosted.org/packages/93/76/1657047da771315911a927b364a32dafce4135b79b64208ce4ac69525c56/multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87", size = 132861 }, + { url = "https://files.pythonhosted.org/packages/19/a5/9f07ffb9bf68b8aaa406c2abee27ad87e8b62a60551587b8e59ee91aea84/multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29", size = 122166 }, + { url = "https://files.pythonhosted.org/packages/95/23/b5ce3318d9d6c8f105c3679510f9d7202980545aad8eb4426313bd8da3ee/multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd", size = 136052 }, + { url = "https://files.pythonhosted.org/packages/ce/5c/02cffec58ffe120873dce520af593415b91cc324be0345f534ad3637da4e/multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8", size = 130094 }, + { url = "https://files.pythonhosted.org/packages/49/f3/3b19a83f4ebf53a3a2a0435f3e447aa227b242ba3fd96a92404b31fb3543/multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df", size = 140962 }, + { url = "https://files.pythonhosted.org/packages/cc/1a/c916b54fb53168c24cb6a3a0795fd99d0a59a0ea93fa9f6edeff5565cb20/multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d", size = 138082 }, + { url = "https://files.pythonhosted.org/packages/ef/1a/dcb7fb18f64b3727c61f432c1e1a0d52b3924016124e4bbc8a7d2e4fa57b/multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b", size = 136019 }, + { url = "https://files.pythonhosted.org/packages/fb/02/7695485375106f5c542574f70e1968c391f86fa3efc9f1fd76aac0af7237/multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626", size = 26676 }, + { url = "https://files.pythonhosted.org/packages/3c/f5/f147000fe1f4078160157b15b0790fff0513646b0f9b7404bf34007a9b44/multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c", size = 28899 }, + { url = "https://files.pythonhosted.org/packages/a4/6c/5df5590b1f9a821154589df62ceae247537b01ab26b0aa85997c35ca3d9e/multidict-6.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80", size = 49151 }, + { url = "https://files.pythonhosted.org/packages/d5/ca/c917fbf1be989cd7ea9caa6f87e9c33844ba8d5fbb29cd515d4d2833b84c/multidict-6.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16", size = 29803 }, + { url = "https://files.pythonhosted.org/packages/22/19/d97086fc96f73acf36d4dbe65c2c4175911969df49c4e94ef082be59d94e/multidict-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e", size = 29947 }, + { url = "https://files.pythonhosted.org/packages/e3/3b/203476b6e915c3f51616d5f87230c556e2f24b168c14818a3d8dae242b1b/multidict-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817", size = 130369 }, + { url = "https://files.pythonhosted.org/packages/c6/4f/67470007cf03b2bb6df8ae6d716a8eeb0a7d19e0c8dba4e53fa338883bca/multidict-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc", size = 135231 }, + { url = "https://files.pythonhosted.org/packages/6d/f5/7a5ce64dc9a3fecc7d67d0b5cb9c262c67e0b660639e5742c13af63fd80f/multidict-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1", size = 133634 }, + { url = "https://files.pythonhosted.org/packages/05/93/ab2931907e318c0437a4cd156c9cfff317ffb33d99ebbfe2d64200a870f7/multidict-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844", size = 131349 }, + { url = "https://files.pythonhosted.org/packages/54/aa/ab8eda83a6a85f5b4bb0b1c28e62b18129b14519ef2e0d4cfd5f360da73c/multidict-6.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48", size = 120861 }, + { url = "https://files.pythonhosted.org/packages/15/2f/7d08ea7c5d9f45786893b4848fad59ec8ea567367d4234691a721e4049a1/multidict-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0", size = 134611 }, + { url = "https://files.pythonhosted.org/packages/8b/07/387047bb1eac563981d397a7f85c75b306df1fff3c20b90da5a6cf6e487e/multidict-6.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f", size = 128955 }, + { url = "https://files.pythonhosted.org/packages/8d/6e/7ae18f764a5282c2d682f1c90c6b2a0f6490327730170139a7a63bf3bb20/multidict-6.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de", size = 139759 }, + { url = "https://files.pythonhosted.org/packages/b6/f4/c1b3b087b9379b9e56229bcf6570b9a963975c205a5811ac717284890598/multidict-6.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02", size = 136426 }, + { url = "https://files.pythonhosted.org/packages/a2/0e/ef7b39b161ffd40f9e25dd62e59644b2ccaa814c64e9573f9bc721578419/multidict-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d", size = 134648 }, + { url = "https://files.pythonhosted.org/packages/37/5c/7905acd0ca411c97bcae62ab167d9922f0c5a1d316b6d3af875d4bda3551/multidict-6.2.0-cp313-cp313-win32.whl", hash = "sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e", size = 26680 }, + { url = "https://files.pythonhosted.org/packages/89/36/96b071d1dad6ac44fe517e4250329e753787bb7a63967ef44bb9b3a659f6/multidict-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2", size = 28942 }, + { url = "https://files.pythonhosted.org/packages/f5/05/d686cd2a12d648ecd434675ee8daa2901a80f477817e89ab3b160de5b398/multidict-6.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7", size = 50807 }, + { url = "https://files.pythonhosted.org/packages/4c/1f/c7db5aac8fea129fa4c5a119e3d279da48d769138ae9624d1234aa01a06f/multidict-6.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b", size = 30474 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/1fb27514f4d73cea165429dcb7d90cdc4a45445865832caa0c50dd545420/multidict-6.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e", size = 30841 }, + { url = "https://files.pythonhosted.org/packages/d6/6b/9487169e549a23c8958edbb332afaf1ab55d61f0c03cb758ee07ff8f74fb/multidict-6.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025", size = 148658 }, + { url = "https://files.pythonhosted.org/packages/d7/22/79ebb2e4f70857c94999ce195db76886ae287b1b6102da73df24dcad4903/multidict-6.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd", size = 151988 }, + { url = "https://files.pythonhosted.org/packages/49/5d/63b17f3c1a2861587d26705923a94eb6b2600e5222d6b0d513bce5a78720/multidict-6.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7", size = 148432 }, + { url = "https://files.pythonhosted.org/packages/a3/22/55204eec45c4280fa431c11494ad64d6da0dc89af76282fc6467432360a0/multidict-6.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af", size = 143161 }, + { url = "https://files.pythonhosted.org/packages/97/e6/202b2cf5af161228767acab8bc49e73a91f4a7de088c9c71f3c02950a030/multidict-6.2.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331", size = 136820 }, + { url = "https://files.pythonhosted.org/packages/7d/16/dbedae0e94c7edc48fddef0c39483f2313205d9bc566fd7f11777b168616/multidict-6.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c", size = 150875 }, + { url = "https://files.pythonhosted.org/packages/f3/04/38ccf25d4bf8beef76a22bad7d9833fd088b4594c9765fe6fede39aa6c89/multidict-6.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b", size = 142050 }, + { url = "https://files.pythonhosted.org/packages/9e/89/4f6b43386e7b79a4aad560d751981a0a282a1943c312ac72f940d7cf8f9f/multidict-6.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151", size = 154117 }, + { url = "https://files.pythonhosted.org/packages/24/e3/3dde5b193f86d30ad6400bd50e116b0df1da3f0c7d419661e3bd79e5ad86/multidict-6.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019", size = 149408 }, + { url = "https://files.pythonhosted.org/packages/df/b2/ec1e27e8e3da12fcc9053e1eae2f6b50faa8708064d83ea25aa7fb77ffd2/multidict-6.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547", size = 145767 }, + { url = "https://files.pythonhosted.org/packages/3a/8e/c07a648a9d592fa9f3a19d1c7e1c7738ba95aff90db967a5a09cff1e1f37/multidict-6.2.0-cp313-cp313t-win32.whl", hash = "sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc", size = 28950 }, + { url = "https://files.pythonhosted.org/packages/dc/a9/bebb5485b94d7c09831638a4df9a1a924c32431a750723f0bf39cd16a787/multidict-6.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44", size = 32001 }, + { url = "https://files.pythonhosted.org/packages/9c/fd/b247aec6add5601956d440488b7f23151d8343747e82c038af37b28d6098/multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530", size = 10266 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[package]] +name = "rich-toolkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/8a/71cfbf6bf6257ea785d1f030c22468f763eea1b3e5417620f2ba9abd6dca/rich_toolkit-0.13.2.tar.gz", hash = "sha256:fea92557530de7c28f121cbed572ad93d9e0ddc60c3ca643f1b831f2f56b95d3", size = 72288 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/1b/1c2f43af46456050b27810a7a013af8a7e12bc545a0cdc00eb0df55eb769/rich_toolkit-0.13.2-py3-none-any.whl", hash = "sha256:f3f6c583e5283298a2f7dbd3c65aca18b7f818ad96174113ab5bec0b0e35ed61", size = 13566 }, +] + +[[package]] +name = "setuptools" +version = "77.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/df/9f719dc48f64284be8bd99e2e0bb0dd6e9f8e2c2c3c7bf7a685bc5adf2c7/setuptools-77.0.1.tar.gz", hash = "sha256:a1246a1b4178c66d7cf50c9fc6d530fac3f89bc284cf803c7fa878c41b1a03b2", size = 1366225 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/50/bc3d02829a3babd70b7f1414c93cf6acd198976f0469a07d0e7b813c5002/setuptools-77.0.1-py3-none-any.whl", hash = "sha256:81a234dff81a82bb52e522c8aef145d0dd4de1fd6de4d3b196d0f77dc2fded26", size = 1254282 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "starlette" +version = "0.46.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, +] + +[[package]] +name = "tiekreis-visualization" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "fastapi", extra = ["standard"] }, + { name = "jinja2" }, + { name = "pydantic-settings" }, + { name = "tierkreis" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", extras = ["standard"], specifier = ">=0.115.11" }, + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "pydantic-settings", specifier = ">=2.8.1" }, + { name = "tierkreis", editable = "../python" }, +] + +[[package]] +name = "tierkreis" +version = "0.7.8" +source = { editable = "../python" } +dependencies = [ + { name = "betterproto", extra = ["compiler"] }, + { name = "click" }, + { name = "graphviz" }, + { name = "grpclib" }, + { name = "networkx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "yachalk" }, +] + +[package.metadata] +requires-dist = [ + { name = "betterproto", extras = ["compiler"], specifier = "==2.0.0b6" }, + { name = "click", specifier = ">=8.1.3,<9" }, + { name = "docker", marker = "extra == 'docker'", specifier = ">=6,<7" }, + { name = "graphviz", specifier = ">=0.20,<0.21" }, + { name = "grpclib", specifier = ">=0.4.3rc0,<0.5" }, + { name = "networkx", specifier = ">=2.6.3,<4" }, + { name = "numpy", marker = "extra == 'sc22-example'", specifier = ">=1.20,<2" }, + { name = "opentelemetry-exporter-otlp", marker = "extra == 'telemetry'", specifier = ">=1.15.0,<2" }, + { name = "opentelemetry-sdk", marker = "extra == 'telemetry'", specifier = ">=1.15.0,<2" }, + { name = "pydantic", specifier = "~=2.5" }, + { name = "pytket", marker = "extra == 'commontypes'", specifier = ">=1.0" }, + { name = "pytket", marker = "extra == 'sc22-example'", specifier = ">=1.0" }, + { name = "requests", specifier = ">=2.31,<3" }, + { name = "tierkreis-typecheck", marker = "extra == 'typecheck'", editable = "../type_check" }, + { name = "yachalk", specifier = ">=0.1.4,<0.2" }, +] +provides-extras = ["docker", "telemetry", "commontypes", "sc22-example", "typecheck"] + +[package.metadata.requires-dev] +build = [{ name = "build", extras = ["uv"] }] +dev = [{ name = "tierkreis", extras = ["telemetry", "typecheck", "docker", "sc22-example", "commontypes"] }] + +[[package]] +name = "typer" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "uvicorn" +version = "0.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, +] + +[[package]] +name = "watchfiles" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/26/c705fc77d0a9ecdb9b66f1e2976d95b81df3cae518967431e7dbf9b5e219/watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205", size = 94625 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/1a/8f4d9a1461709756ace48c98f07772bc6d4519b1e48b5fa24a4061216256/watchfiles-1.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2", size = 391345 }, + { url = "https://files.pythonhosted.org/packages/bc/d2/6750b7b3527b1cdaa33731438432e7238a6c6c40a9924049e4cebfa40805/watchfiles-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9", size = 381515 }, + { url = "https://files.pythonhosted.org/packages/4e/17/80500e42363deef1e4b4818729ed939aaddc56f82f4e72b2508729dd3c6b/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712", size = 449767 }, + { url = "https://files.pythonhosted.org/packages/10/37/1427fa4cfa09adbe04b1e97bced19a29a3462cc64c78630787b613a23f18/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12", size = 455677 }, + { url = "https://files.pythonhosted.org/packages/c5/7a/39e9397f3a19cb549a7d380412fd9e507d4854eddc0700bfad10ef6d4dba/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844", size = 482219 }, + { url = "https://files.pythonhosted.org/packages/45/2d/7113931a77e2ea4436cad0c1690c09a40a7f31d366f79c6f0a5bc7a4f6d5/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733", size = 518830 }, + { url = "https://files.pythonhosted.org/packages/f9/1b/50733b1980fa81ef3c70388a546481ae5fa4c2080040100cd7bf3bf7b321/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af", size = 497997 }, + { url = "https://files.pythonhosted.org/packages/2b/b4/9396cc61b948ef18943e7c85ecfa64cf940c88977d882da57147f62b34b1/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a", size = 452249 }, + { url = "https://files.pythonhosted.org/packages/fb/69/0c65a5a29e057ad0dc691c2fa6c23b2983c7dabaa190ba553b29ac84c3cc/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff", size = 614412 }, + { url = "https://files.pythonhosted.org/packages/7f/b9/319fcba6eba5fad34327d7ce16a6b163b39741016b1996f4a3c96b8dd0e1/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e", size = 611982 }, + { url = "https://files.pythonhosted.org/packages/f1/47/143c92418e30cb9348a4387bfa149c8e0e404a7c5b0585d46d2f7031b4b9/watchfiles-1.0.4-cp312-cp312-win32.whl", hash = "sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94", size = 271822 }, + { url = "https://files.pythonhosted.org/packages/ea/94/b0165481bff99a64b29e46e07ac2e0df9f7a957ef13bec4ceab8515f44e3/watchfiles-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c", size = 285441 }, + { url = "https://files.pythonhosted.org/packages/11/de/09fe56317d582742d7ca8c2ca7b52a85927ebb50678d9b0fa8194658f536/watchfiles-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90", size = 277141 }, + { url = "https://files.pythonhosted.org/packages/08/98/f03efabec64b5b1fa58c0daab25c68ef815b0f320e54adcacd0d6847c339/watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9", size = 390954 }, + { url = "https://files.pythonhosted.org/packages/16/09/4dd49ba0a32a45813debe5fb3897955541351ee8142f586303b271a02b40/watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60", size = 381133 }, + { url = "https://files.pythonhosted.org/packages/76/59/5aa6fc93553cd8d8ee75c6247763d77c02631aed21551a97d94998bf1dae/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407", size = 449516 }, + { url = "https://files.pythonhosted.org/packages/4c/aa/df4b6fe14b6317290b91335b23c96b488d365d65549587434817e06895ea/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d", size = 454820 }, + { url = "https://files.pythonhosted.org/packages/5e/71/185f8672f1094ce48af33252c73e39b48be93b761273872d9312087245f6/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d", size = 481550 }, + { url = "https://files.pythonhosted.org/packages/85/d7/50ebba2c426ef1a5cb17f02158222911a2e005d401caf5d911bfca58f4c4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b", size = 518647 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/4c009342e393c545d68987e8010b937f72f47937731225b2b29b7231428f/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590", size = 497547 }, + { url = "https://files.pythonhosted.org/packages/0f/7c/1cf50b35412d5c72d63b2bf9a4fffee2e1549a245924960dd087eb6a6de4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902", size = 452179 }, + { url = "https://files.pythonhosted.org/packages/d6/a9/3db1410e1c1413735a9a472380e4f431ad9a9e81711cda2aaf02b7f62693/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1", size = 614125 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/0025d365cf6248c4d1ee4c3d2e3d373bdd3f6aff78ba4298f97b4fad2740/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303", size = 611911 }, + { url = "https://files.pythonhosted.org/packages/55/55/035838277d8c98fc8c917ac9beeb0cd6c59d675dc2421df5f9fcf44a0070/watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80", size = 271152 }, + { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, +] + +[[package]] +name = "yachalk" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-resources" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/df/039d7b2b02bf36187c4b1ec8871f12640763fce83eae150d0b435886d2ea/yachalk-0.1.7.tar.gz", hash = "sha256:4f2ac54adef924fda03756a94c8b4b780bf8aecf80bbd6afa80e9f74d6cecc5f", size = 17962 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/a0/523a7cad1fafaaa98bd5716c76eae545a58c6d53884c1be4a27e65c2b203/yachalk-0.1.7-py3-none-any.whl", hash = "sha256:1a97fd492dbbfef5bf347a3718d469b74285cb8e15a417bfb41b20eb103ba80f", size = 13851 }, +] diff --git a/uv.lock b/uv.lock index 15b13fc..0122597 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.10, <3.13" [manifest] @@ -7,7 +8,9 @@ members = [ "tierkreis", "tierkreis-typecheck", ] -requirements = [ + +[manifest.dependency-groups] +dev = [ { name = "pydantic", specifier = ">=2.9.2" }, { name = "pyright", specifier = "==1.1.373" }, { name = "pytest", specifier = ">=6.2,<9" }, @@ -17,6 +20,20 @@ requirements = [ { name = "sphinx", specifier = ">=4.3" }, { name = "sphinx-book-theme", specifier = ">=1.1.2" }, ] +docs = [ + { name = "sphinx", specifier = ">=4.3" }, + { name = "sphinx-book-theme", specifier = ">=1.1.2" }, +] +lint = [ + { name = "pyright", specifier = "==1.1.373" }, + { name = "ruff", specifier = "~=0.5" }, +] +test = [ + { name = "pydantic", specifier = ">=2.9.2" }, + { name = "pytest", specifier = ">=6.2,<9" }, + { name = "pytest-asyncio", specifier = ">=0.16,<0.17" }, + { name = "pytest-cov", specifier = ">=5.0,<6" }, +] [[package]] name = "accessible-pygments" @@ -119,6 +136,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, ] +[[package]] +name = "build" +version = "1.2.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950 }, +] + +[package.optional-dependencies] +uv = [ + { name = "uv" }, +] + [[package]] name = "certifi" version = "2024.8.30" @@ -187,7 +225,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -904,6 +942,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, ] +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, +] + [[package]] name = "pyright" version = "1.1.373" @@ -1251,7 +1298,7 @@ wheels = [ [[package]] name = "tierkreis" -version = "0.7.2" +version = "0.7.8" source = { editable = "python" } dependencies = [ { name = "betterproto", extra = ["compiler"] }, @@ -1284,13 +1331,11 @@ typecheck = [ ] [package.dev-dependencies] +build = [ + { name = "build", extra = ["uv"] }, +] dev = [ - { name = "docker" }, - { name = "numpy" }, - { name = "opentelemetry-exporter-otlp" }, - { name = "opentelemetry-sdk" }, - { name = "pytket" }, - { name = "tierkreis-typecheck" }, + { name = "tierkreis", extra = ["commontypes", "docker", "sc22-example", "telemetry", "typecheck"] }, ] [package.metadata] @@ -1311,6 +1356,7 @@ requires-dist = [ { name = "tierkreis-typecheck", marker = "extra == 'typecheck'", editable = "type_check" }, { name = "yachalk", specifier = ">=0.1.4,<0.2" }, ] +provides-extras = ["docker", "telemetry", "commontypes", "sc22-example", "typecheck"] [package.metadata.requires-dev] build = [{ name = "build", extras = ["uv"] }] @@ -1348,6 +1394,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, ] +[[package]] +name = "uv" +version = "0.6.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/c5/6d5a98618437255a70106338a5e3aaf154b18e3ef0e0313bbe79791cd792/uv-0.6.6.tar.gz", hash = "sha256:abf8be1e056f9d36ddda9c3c8c07510f6d4fe61915d4cd797374756f58249c81", size = 3071736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/7a/a01226a4c2282afcab0e518082772cc3f5133c325d428f8e298c1aac7c5a/uv-0.6.6-py3-none-linux_armv6l.whl", hash = "sha256:8a6d2aca8794e72e2e68ebfae06b8697bb0ea8a8d016229109125d364f743b7a", size = 15662414 }, + { url = "https://files.pythonhosted.org/packages/41/13/0258d919d97358516a670c5ca354e0fb6af8bdd2caa3c8e141c55547d426/uv-0.6.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c55f1ebc980bd4a2013999e0a22e2796deb08b56c7815942d74ba23abce8d4fc", size = 15604372 }, + { url = "https://files.pythonhosted.org/packages/5b/81/cbc733571f07d1177f95c4b531756db1fd2e348f2105a0ac93527d5e0d10/uv-0.6.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4b41b3024ca55c17e7dfda1e907249e598379a8202d2a76e05018156a1c0501", size = 14536284 }, + { url = "https://files.pythonhosted.org/packages/e8/23/d29f270e0b6bf8a2af9bef4af4e43f47873373dfd7c7f031b75f50d0596b/uv-0.6.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:7f0836ba3d9c979e744a0991b212934877b786541fd5c9ee7eff99a3f8c9dd6a", size = 14971148 }, + { url = "https://files.pythonhosted.org/packages/fc/c9/5c218dafe1135bbbf0ab9174686344554645f8ebe908351079f31c4bfc57/uv-0.6.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8733355d21e325bb896bd2b7bc37bbcb888097d532ce14265efbb53beaf07ca0", size = 15391689 }, + { url = "https://files.pythonhosted.org/packages/be/6a/e8e363458096e00841d205fbfa502a94e986284111bdd0b5130e952bcb90/uv-0.6.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af832fe366bc6174fe822b968bbeb1bb1f8aeb42418941281a696257a5669bb7", size = 15957340 }, + { url = "https://files.pythonhosted.org/packages/66/88/110b95b9bc8652c24176fdca74cc317f9558dddf6737158d3df65bfb64ab/uv-0.6.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c05f376f56a925d43304ee02d2915df002497fa1c3f51908252b868704131c32", size = 16898780 }, + { url = "https://files.pythonhosted.org/packages/9d/f5/20793e443af05c4755e8e7ead85b6fd70073204682e34eced190786d33bc/uv-0.6.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8105a72d6502d5a5fbf404afa1124afe6501878ce6a05caeac29e457cea2785", size = 16628180 }, + { url = "https://files.pythonhosted.org/packages/c7/f9/90ad562eec31c5aa20987964450606d8080c1e0eafb5b303be7cdb1dfd57/uv-0.6.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f4934dbfff8ea30800aeda5e9336fc2dc06710f3a6216fac783bc63f98fc54", size = 20832699 }, + { url = "https://files.pythonhosted.org/packages/14/65/84399efca40f3abf51958f289b65b5ae9e643817e9ed98defbe4da97efca/uv-0.6.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe1f84bced6d373a02d8b960efc3a4b65d63ab19e1fdc4f12a56a483d687f4db", size = 16233044 }, + { url = "https://files.pythonhosted.org/packages/26/5f/c7534ae000a31d4eca939668234ec385abab925b28d1514a6c5f01155384/uv-0.6.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:257b44eb43790c1cde59527f53efd1263528bf791959c94be40c3d32c8ac4e6d", size = 15254345 }, + { url = "https://files.pythonhosted.org/packages/8a/70/9df763ee88b054729118ca4caf5479160d741a2e3303a81f5c447c9b76ff/uv-0.6.6-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:5a6839ff6cdaa2fc7864ae893d725d04dd914e36fb20f64c6603edc4a17dfe78", size = 15396565 }, + { url = "https://files.pythonhosted.org/packages/15/3d/231379ca356cd3468633d712e099e801b597a06f891f3bb7ec3aed2c071a/uv-0.6.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:1d62a3fb6fdbb05518e5124950d252033908e8e2dd98e17c63fd9b0aa807da6f", size = 15574407 }, + { url = "https://files.pythonhosted.org/packages/d1/4d/e3a00a5cd318ba6d131c1d566f87cc449b54fc84b9010af0b5bfa252bd36/uv-0.6.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:61f13d8af0aad5b1f9332fee2cd7eeeec5cf93634c5071bbbcf5d338a0920098", size = 16375912 }, + { url = "https://files.pythonhosted.org/packages/77/ef/511a9ac6cd732e5ba581426bd9f9983606511c2e676f696dbd1b7a9c72c0/uv-0.6.6-py3-none-win32.whl", hash = "sha256:419e8cd84db545a0880223fd0a042c063a1412179903797a87f5bd0d1613cdbd", size = 15720370 }, + { url = "https://files.pythonhosted.org/packages/7b/d4/8f2df45ef1cfb645f38e48595532c8406658f702a330f5d002033e84ebfd/uv-0.6.6-py3-none-win_amd64.whl", hash = "sha256:c9802cac1fb9cbff97d1adf2c2516f2f368eea60c7d6a8e3a474f2bca7b44c6c", size = 17110840 }, + { url = "https://files.pythonhosted.org/packages/6b/bc/9cf8ffe31607e32bc1de05edea2c11158b3aa7309cffc8e59ec7409a4988/uv-0.6.6-py3-none-win_arm64.whl", hash = "sha256:b804a7f8f37c109e714ce02084735cc39a96b7e3062e58420120fe4798a65ef1", size = 15930063 }, +] + [[package]] name = "websocket-client" version = "1.8.0"