From 8464d8c718eda1a64717ec6f99d78056de69f4fe Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 25 Nov 2024 18:04:03 +0000 Subject: [PATCH] add rust and update python --- .cargo/config.toml | 13 + .dockerignore | 19 + .github/workflows/build-typecheck.yaml | 162 + .github/workflows/deploy_docker_image.yaml | 65 + .github/workflows/docs.yml | 6 + .github/workflows/pr_server_config.json | 9 + .github/workflows/pull-request.yaml | 134 + .github/workflows/pytket_integration.yaml | 98 + .../workflows/pytket_integration_config.json | 13 + .github/workflows/run_with_server | 10 + .gitignore | 167 +- Cargo.lock | 2954 +++++++++++++++++ Cargo.toml | 43 + Dockerfile | 22 + LICENCE | 201 ++ README.md | 24 + devenv.lock | 202 ++ devenv.nix | 35 + devenv.yaml | 10 + integration/README.md | 1 + integration/pyproject.toml | 20 + integration/tests/__init__.py | 8 + integration/tests/conftest.py | 121 + integration/tests/managers/__init__.py | 0 integration/tests/managers/docker_manager.py | 208 ++ integration/tests/managers/local_manager.py | 129 + integration/tests/test_cli.py | 108 + integration/tests/test_scoping.py | 260 ++ integration/tests/test_server.py | 236 ++ justfile | 30 + pyproject.toml | 26 + python/.gitignore | 2 + python/README.md | 6 +- python/examples/variational-viz.ipynb | 16 +- python/examples/variational.ipynb | 14 +- python/examples/viz_demo.ipynb | 12 +- .../controller/runtime_storage_api.proto | 49 + .../v1alpha1/controller/tierkreis.proto | 26 + python/protos/v1alpha1/graph.proto | 180 +- python/protos/v1alpha1/runtime.proto | 27 +- python/protos/v1alpha1/signature.proto | 112 +- python/protos/v1alpha1/worker.proto | 16 + python/pyproject.toml | 32 +- python/pytket_worker/pytket_worker/main.py | 1 + python/tests/test_builder.py | 36 +- python/tests/test_frontend.py | 22 +- python/tests/test_generics.py | 14 +- python/tests/test_type_check.py | 12 +- python/tests/test_variants.py | 57 +- python/tierkreis/builder.py | 10 +- python/tierkreis/cli.py | 8 +- python/tierkreis/core/_internal.py | 2 +- python/tierkreis/core/graphviz.py | 11 +- python/tierkreis/core/type_errors.py | 6 +- python/tierkreis/core/type_inference.py | 2 +- python/tierkreis/core/types.py | 9 + python/tierkreis/core/values.py | 17 +- python/tierkreis/pyruntime/python_builtin.py | 48 +- python/tierkreis/pyruntime/python_runtime.py | 10 +- python/tierkreis/worker/prelude.py | 2 +- tierkreis-core/Cargo.toml | 36 + tierkreis-core/README.md | 4 + tierkreis-core/src/builtins.rs | 885 +++++ tierkreis-core/src/graph.rs | 962 ++++++ tierkreis-core/src/lib.rs | 77 + tierkreis-core/src/namespace.rs | 298 ++ tierkreis-core/src/portgraph/dot.rs | 56 + tierkreis-core/src/portgraph/graph.rs | 1076 ++++++ tierkreis-core/src/portgraph/mod.rs | 93 + tierkreis-core/src/portgraph/substitute.rs | 299 ++ tierkreis-core/src/portgraph/toposort.rs | 82 + tierkreis-core/src/prelude.rs | 48 + tierkreis-core/src/symbol.rs | 344 ++ tierkreis-core/src/type_checker/extract.rs | 30 + tierkreis-core/src/type_checker/mod.rs | 1455 ++++++++ tierkreis-core/src/type_checker/solve.rs | 939 ++++++ tierkreis-core/src/type_checker/union_find.rs | 82 + tierkreis-core/src/type_checker/visit.rs | 533 +++ tierkreis-proto/Cargo.toml | 48 + tierkreis-proto/README.md | 7 + tierkreis-proto/build.rs | 20 + tierkreis-proto/protos/v1alpha1 | 1 + tierkreis-proto/src/graph.rs | 571 ++++ tierkreis-proto/src/lib.rs | 158 + tierkreis-proto/src/messages.rs | 802 +++++ tierkreis-proto/src/messages/graph_trace.rs | 319 ++ tierkreis-proto/src/protos_gen.rs | 39 + tierkreis-proto/src/signature.rs | 231 ++ tierkreis-proto/src/worker.rs | 12 + tierkreis-runtime/Cargo.toml | 58 + tierkreis-runtime/README.md | 4 + tierkreis-runtime/src/lib.rs | 1902 +++++++++++ tierkreis-runtime/src/operations/box.rs | 48 + tierkreis-runtime/src/operations/eval.rs | 86 + tierkreis-runtime/src/operations/function.rs | 78 + tierkreis-runtime/src/operations/graph.rs | 448 +++ .../src/operations/graph/checkpoint_client.rs | 108 + tierkreis-runtime/src/operations/mod.rs | 1102 ++++++ tierkreis-runtime/src/operations/variant.rs | 128 + tierkreis-runtime/src/util.rs | 35 + tierkreis-runtime/src/workers/external.rs | 703 ++++ tierkreis-runtime/src/workers/local.rs | 148 + tierkreis-runtime/src/workers/mod.rs | 70 + tierkreis-server/Cargo.toml | 64 + tierkreis-server/Dockerfile | 10 + tierkreis-server/README.md | 4 + tierkreis-server/src/grpc.rs | 274 ++ tierkreis-server/src/main.rs | 348 ++ tierkreis-server/src/server.rs | 465 +++ type_check/Cargo.toml | 33 + type_check/README.md | 3 + type_check/py.typed | 0 type_check/pyproject.toml | 24 + type_check/src/lib.rs | 82 + type_check/tierkreis_typecheck.pyi | 7 + uv.lock | 1419 ++++++++ 116 files changed, 23106 insertions(+), 145 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .dockerignore create mode 100644 .github/workflows/build-typecheck.yaml create mode 100644 .github/workflows/deploy_docker_image.yaml create mode 100644 .github/workflows/pr_server_config.json create mode 100644 .github/workflows/pull-request.yaml create mode 100644 .github/workflows/pytket_integration.yaml create mode 100644 .github/workflows/pytket_integration_config.json create mode 100755 .github/workflows/run_with_server create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 LICENCE create mode 100644 README.md create mode 100644 devenv.lock create mode 100644 devenv.nix create mode 100644 devenv.yaml create mode 100644 integration/README.md create mode 100644 integration/pyproject.toml create mode 100644 integration/tests/__init__.py create mode 100644 integration/tests/conftest.py create mode 100644 integration/tests/managers/__init__.py create mode 100644 integration/tests/managers/docker_manager.py create mode 100644 integration/tests/managers/local_manager.py create mode 100644 integration/tests/test_cli.py create mode 100644 integration/tests/test_scoping.py create mode 100644 integration/tests/test_server.py create mode 100644 justfile create mode 100644 pyproject.toml create mode 100644 python/.gitignore create mode 100644 python/protos/v1alpha1/controller/runtime_storage_api.proto create mode 100644 python/protos/v1alpha1/controller/tierkreis.proto mode change 100644 => 100755 python/pytket_worker/pytket_worker/main.py create mode 100644 tierkreis-core/Cargo.toml create mode 100644 tierkreis-core/README.md create mode 100644 tierkreis-core/src/builtins.rs create mode 100644 tierkreis-core/src/graph.rs create mode 100644 tierkreis-core/src/lib.rs create mode 100644 tierkreis-core/src/namespace.rs create mode 100644 tierkreis-core/src/portgraph/dot.rs create mode 100644 tierkreis-core/src/portgraph/graph.rs create mode 100644 tierkreis-core/src/portgraph/mod.rs create mode 100644 tierkreis-core/src/portgraph/substitute.rs create mode 100644 tierkreis-core/src/portgraph/toposort.rs create mode 100644 tierkreis-core/src/prelude.rs create mode 100644 tierkreis-core/src/symbol.rs create mode 100644 tierkreis-core/src/type_checker/extract.rs create mode 100644 tierkreis-core/src/type_checker/mod.rs create mode 100644 tierkreis-core/src/type_checker/solve.rs create mode 100644 tierkreis-core/src/type_checker/union_find.rs create mode 100644 tierkreis-core/src/type_checker/visit.rs create mode 100644 tierkreis-proto/Cargo.toml create mode 100644 tierkreis-proto/README.md create mode 100644 tierkreis-proto/build.rs create mode 120000 tierkreis-proto/protos/v1alpha1 create mode 100644 tierkreis-proto/src/graph.rs create mode 100644 tierkreis-proto/src/lib.rs create mode 100644 tierkreis-proto/src/messages.rs create mode 100644 tierkreis-proto/src/messages/graph_trace.rs create mode 100644 tierkreis-proto/src/protos_gen.rs create mode 100644 tierkreis-proto/src/signature.rs create mode 100644 tierkreis-proto/src/worker.rs create mode 100644 tierkreis-runtime/Cargo.toml create mode 100644 tierkreis-runtime/README.md create mode 100644 tierkreis-runtime/src/lib.rs create mode 100644 tierkreis-runtime/src/operations/box.rs create mode 100644 tierkreis-runtime/src/operations/eval.rs create mode 100644 tierkreis-runtime/src/operations/function.rs create mode 100644 tierkreis-runtime/src/operations/graph.rs create mode 100644 tierkreis-runtime/src/operations/graph/checkpoint_client.rs create mode 100644 tierkreis-runtime/src/operations/mod.rs create mode 100644 tierkreis-runtime/src/operations/variant.rs create mode 100644 tierkreis-runtime/src/util.rs create mode 100644 tierkreis-runtime/src/workers/external.rs create mode 100644 tierkreis-runtime/src/workers/local.rs create mode 100644 tierkreis-runtime/src/workers/mod.rs create mode 100644 tierkreis-server/Cargo.toml create mode 100644 tierkreis-server/Dockerfile create mode 100644 tierkreis-server/README.md create mode 100644 tierkreis-server/src/grpc.rs create mode 100644 tierkreis-server/src/main.rs create mode 100644 tierkreis-server/src/server.rs create mode 100644 type_check/Cargo.toml create mode 100644 type_check/README.md create mode 100644 type_check/py.typed create mode 100644 type_check/pyproject.toml create mode 100644 type_check/src/lib.rs create mode 100644 type_check/tierkreis_typecheck.pyi create mode 100644 uv.lock diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..212fb09 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,13 @@ +[registries.artifactory] +index = "https://quantinuumsw.jfrog.io/artifactory/git/rust_local.git" + +[net] +git-fetch-with-cli = true + +# This allows building the type-check (pyo3) module on MacOSX "Apple Silicon" +# (cargo build from root directory ignores any config.toml in subdirectories.) +[target.aarch64-apple-darwin] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..409fefe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ + +**/target +**/.git +**/.venv +**/venv +**/.vscode +**/.pytest-cache +**/.mypy-cache +**/__pycache__ +**/.direnv +**/.DS_Store +**/Thumbs.db + +**/.ruff_cache +**/build +**/tests +**/Dockerfile +.env +cqcpython-secret.txt \ No newline at end of file diff --git a/.github/workflows/build-typecheck.yaml b/.github/workflows/build-typecheck.yaml new file mode 100644 index 0000000..37705bd --- /dev/null +++ b/.github/workflows/build-typecheck.yaml @@ -0,0 +1,162 @@ +name: Build python + +on: + workflow_dispatch + +jobs: + build_core: + name: Build tierkreis package + runs-on: ubuntu-latest + defaults: + run: + working-directory: python + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install Protoc + uses: arduino/setup-protoc@v3 + + - name: build main package + run: | + pipx run build[uv] --installer=uv + + - run: pipx twine check dist/* + + - uses: actions/upload-artifact@v3 + with: + name: core_build + path: python/dist + + build_wheels: + name: build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + # os: [ubuntu] + target: [x86_64, aarch64] + # target: [x86_64] + manylinux: [auto] + include: + - os: ubuntu + platform: linux + - os: windows + ls: dir + - os: macos + target: aarch64 + + + # - os: windows + # ls: dir + # target: i686 + # python-architecture: x86 + # - os: ubuntu + # platform: linux + # target: i686 + # musllinux + # - os: ubuntu + # platform: linux + # target: x86_64 + # manylinux: musllinux_1_1 + # - os: ubuntu + # platform: linux + # target: aarch64 + # manylinux: musllinux_1_1 + exclude: + # Windows on arm64 only supports Python 3.11+ + - os: windows + target: aarch64 + runs-on: ${{ matrix.os }}-latest + steps: + - uses: actions/checkout@v3 + + + - name: build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + manylinux: ${{ matrix.manylinux || 'auto' }} + container: ${{ matrix.container }} + args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12' }} -m type_check/Cargo.toml + rust-toolchain: stable + rustup-components: "rustfmt" + + - run: ${{ matrix.ls || 'ls -lh' }} dist/ + + - run: pipx twine check dist/* + + - uses: actions/upload-artifact@v4 + with: + name: type_wheels + path: dist + + + test_wheels: + needs: [build_core, build_wheels] + name: test on ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.python-version }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + python-version: ['3.10', '3.11', '3.12'] + include: + - os: ubuntu + platform: manylinux + arch: x86_64 + - os: windows + platform: win + arch: amd64 + - os: macos + platform: macos + arch: x86_64 + + runs-on: ${{ matrix.os }}-latest + defaults: + run: + working-directory: python + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + + + - name: Download core + uses: actions/download-artifact@v4 + with: + name: core_build + path: python/dist/ + - name: Install + run: | + pip install $(find dist -type f -iname "tierkreis-*.whl")'[docker,telemetry,commontypes,sc22-example]' + pip install pytest + shell: bash + - name: Test with pytest + run: | + pytest + + - name: Download wheels + uses: actions/download-artifact@v4 + with: + name: type_wheels + path: python/type_wheels/ + - name: Install type_check + run: | + export PYVER=$(echo ${{ matrix.python-version }} | tr -d .) + export PLATFORM=${{ matrix.platform || matrix.os }} + export WHL=$(find type_wheels -type f -iname "tierkreis_typecheck*${PYVER}*-${PLATFORM}*${{ matrix.arch }}.whl") + pip install $WHL + shell: bash + - name: Test with pytest + run: | + pytest diff --git a/.github/workflows/deploy_docker_image.yaml b/.github/workflows/deploy_docker_image.yaml new file mode 100644 index 0000000..5981194 --- /dev/null +++ b/.github/workflows/deploy_docker_image.yaml @@ -0,0 +1,65 @@ +name: Deploy Docker Image + +on: + workflow_dispatch: + inputs: + image-tag: + required: true + type: string + aws-address: + type: string + default: '541083330167.dkr.ecr.us-west-2.amazonaws.com' + aws-repo-name: + type: string + default: 'tierkreis/runtime' + github-image-name: + type: string + default: tierkreis + +env: + IMAGE_NAME: ${{ inputs.github-image-name }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure AWS credentials + run: | + aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }} + aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws configure set region us-west-2 + + - name: Login to container registry + run: | + aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin ${{ inputs.aws-address }} + + - name: Build and push docker image + run: | + DOCKER_BUILDKIT=1 docker build . \ + -f ./tierkreis-server/Dockerfile \ + --build-arg GIT_SHA=${{ github.sha }} \ + -t ${{ inputs.aws-address }}/${{ inputs.aws-repo-name }}:${{ inputs.image-tag }} + echo ${{ inputs.aws-address }}/${{ inputs.aws-repo-name }}:${{ inputs.image-tag }} + docker push ${{ inputs.aws-address }}/${{ inputs.aws-repo-name }}:${{ inputs.image-tag }} + + - name: List deployed images + run: aws ecr list-images --repository-name ${{ inputs.aws-repo-name }} --region us-west-2 + + - name: Log in to GitHub container registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + + - name: Build the Docker image + run: docker tag ${{ inputs.aws-address }}/${{ inputs.aws-repo-name }}:${{ inputs.image-tag }} $IMAGE_NAME + + - name: Push the Docker image + run: | + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + # Change all uppercase to lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + echo $IMAGE_ID + docker tag $IMAGE_NAME $IMAGE_ID:${{ inputs.image-tag }} + docker push $IMAGE_ID:${{ inputs.image-tag }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3db4cac..d5be2e9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,6 +20,9 @@ concurrency: jobs: build: + defaults: + run: + working-directory: python name: Build docs. runs-on: ubuntu-latest steps: @@ -44,6 +47,9 @@ jobs: publish: name: Publish docs. + defaults: + run: + working-directory: python environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/pr_server_config.json b/.github/workflows/pr_server_config.json new file mode 100644 index 0000000..e7fd1d1 --- /dev/null +++ b/.github/workflows/pr_server_config.json @@ -0,0 +1,9 @@ +{ + "port": 8090, + "worker_path": [ + { + "location": "python_nodes", + "path": "python/tests/test_worker" + } + ] +} diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml new file mode 100644 index 0000000..014a49e --- /dev/null +++ b/.github/workflows/pull-request.yaml @@ -0,0 +1,134 @@ +name: Pull Request + +on: + pull_request: + branches: + - "main" + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "--cfg=ci_run" + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + UV_VERSION: "0.4.29" + UV_FROZEN: 1 + UV_EXTRA_INDEX_URL: "https://github_actions:${{secrets.PRIVATE_PYPI_PASS}}@cqcpythonrepository.azurewebsites.net/simple/" + +jobs: + check-rust: + name: Check Rust + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.10'] + run-type: ["lint", "test"] + + steps: + - uses: actions/checkout@v4 + + - uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Set up uv + uses: astral-sh/setup-uv@v3 + with: + version: ${{ env.UV_VERSION }} + enable-cache: true + if: ${{ matrix.run-type == 'test' }} + - name: Setup dependencies. + run: uv sync --python ${{ matrix.python-version }} + if: ${{ matrix.run-type == 'test' }} + + - name: Install Protoc + uses: arduino/setup-protoc@v3 + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + # if: ${{ matrix.run-type == 'test' }} + + - name: "Run tests" + run: | + (cd tierkreis-server && cargo build) + uv run cargo test --verbose + if: ${{ matrix.run-type == 'test' }} + + - name: "Check code with rustfmt" + run: cargo fmt -- --check + if: ${{ matrix.run-type == 'lint' }} + + - name: "Lint code with clippy" + run: | + cargo clippy -- -D warnings + if: ${{ matrix.run-type == 'lint' }} + + - name: "Generate crate docs" + run: cargo doc --no-deps --all-features --workspace + env: + RUSTDOCFLAGS: "-Dwarnings" + if: ${{ matrix.run-type == 'lint' }} + + - uses: actions/upload-artifact@v4 + with: + name: tierkreis-server + path: target/debug/tierkreis-server + if: ${{ matrix.run-type == 'test' }} + + check-python: + name: Check tierkreis python + runs-on: ubuntu-latest + needs: check-rust + + strategy: + matrix: + python-version: ['3.10'] + + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Set up uv + uses: astral-sh/setup-uv@v3 + with: + version: ${{ env.UV_VERSION }} + enable-cache: true + + - name: Install Protoc + uses: arduino/setup-protoc@v3 + + - name: Setup dependencies. + run: uv sync --python ${{ matrix.python-version }} + + - name: Check formatting with ruff + run: uv run ruff format --check + + - name: Lint with ruff + run: uv run ruff check + + - name: Check static typing with pyright + run: | + (cd python && uv run pyright) + (cd integration && uv run pyright) + + - uses: actions/download-artifact@v4 + with: + name: tierkreis-server + path: target/debug + + - name: Setup Graphviz + uses: ts-graphviz/setup-graphviz@v2 + + - name: Test with pytest + env: + SERVER: target/debug/tierkreis-server + CONFIG_FILE: .github/workflows/pr_server_config.json + RUN_SCRIPT: .github/workflows/run_with_server + run: | + uv run pytest python + chmod +x $SERVER $RUN_SCRIPT + # run pytest with local server + uv run $RUN_SCRIPT $SERVER $CONFIG_FILE pytest python --host=localhost --port=8090 --client-only + + - run: uv run pytest integration diff --git a/.github/workflows/pytket_integration.yaml b/.github/workflows/pytket_integration.yaml new file mode 100644 index 0000000..130e8dd --- /dev/null +++ b/.github/workflows/pytket_integration.yaml @@ -0,0 +1,98 @@ +name: Pytket Worker integration test + +on: + workflow_dispatch: + push: + branches: + - main + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "--cfg=ci_run" + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + UV_VERSION: "0.4.29" + +jobs: + test: + name: Test Pytket Worker + SC22 Example + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.10'] + server-type: ["rust", "python"] + + steps: + - uses: actions/checkout@v4 + + - uses: mozilla-actions/sccache-action@v0.0.3 + if: ${{ matrix.server-type == 'rust' }} + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2.2.2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Protoc + uses: arduino/setup-protoc@v3 + + - name: Build server + run: cd tierkreis-server && cargo build + if: ${{ matrix.server-type == 'rust' }} + + - name: Install poetry (for pytket worker) + uses: snok/install-poetry@v1 + with: + version: 1.7.1 + + - name: Build python wheel + run: | + set -e + pip install --upgrade build + python -m build --outdir dist ./python/ + + - name: Retarget pytket_worker dependency onto wheel + run: | + set -e + set -x # Echo out the resolution of wildcard below + cd python/pytket_worker + poetry add $(readlink -f $(pwd)/../../dist/*.whl) + + - name: Install (Rust) + run: | + set -e + pip install $(ls dist/*.whl)'[sc22-example,runtime,tksl]' + (cd python/pytket_worker && poetry install) + if: ${{ matrix.server-type == 'rust' }} + + - name: Install (Python) + run: | + set -e + set -x + (cd python/pytket_worker && poetry build) + pip install ./python/pytket_worker/dist/*.whl $(ls ./dist/*.whl)'[sc22-example,test]' + pip install pytest pytest-asyncio + if: ${{ matrix.server-type == 'python' }} + + - name: Pytest pytket_worker (python only) + # The --import-mode gets pytest to use the installed tierkreis package + # (with Rust binaries), rather than the sources in the tierkreis directory. + # Run the pytket tests (off by default), and only the pytket tests + run: cd python && pytest --import-mode=append --pytket -m pytket + if: ${{ matrix.server-type == 'python' }} + + - name: Test from command line (Rust) + env: + SERVER: target/debug/tierkreis-server + CONFIG_FILE: .github/workflows/pytket_integration_config.json + RUN_SCRIPT: .github/workflows/run_with_server + run: | + # run pytest with local server + $RUN_SCRIPT $SERVER $CONFIG_FILE "cd ./python/examples && python sc22_example.py localhost:8090" + if: ${{ matrix.server-type == 'rust' }} + + - name: Test from command line (Python) + run: python ./python/examples/sc22_example.py # No host/port --> PyRuntime + if: ${{ matrix.server-type == 'python' }} diff --git a/.github/workflows/pytket_integration_config.json b/.github/workflows/pytket_integration_config.json new file mode 100644 index 0000000..bd56269 --- /dev/null +++ b/.github/workflows/pytket_integration_config.json @@ -0,0 +1,13 @@ +{ + "port": 8090, + "worker_path": [ + { + "location": "pytket", + "path": "python/pytket_worker/pytket_worker" + }, + { + "location": "sc22", + "path": "python/examples/sc22_worker" + } + ] +} diff --git a/.github/workflows/run_with_server b/.github/workflows/run_with_server new file mode 100755 index 0000000..0923c10 --- /dev/null +++ b/.github/workflows/run_with_server @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# usage run_with_server +set -e # exit+fail if any command fails + +rm -f _server_output.txt +touch _server_output.txt +$1 -C $2 >>_server_output.txt 2>&1 & +tail -f _server_output.txt | grep --line-buffered "Server started" | (head -n1 && killall tail) +bash -c "${*:3}" # This does not do any further expansion +kill -INT %1 diff --git a/.gitignore b/.gitignore index e652560..89ac765 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,165 @@ -./venv -tierkreis/core/protos/ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ *.egg-info/ -**/__pycache__/ \ No newline at end of file +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# ignore VS Code config folder +.vscode + + +.python-version + +.envrc +.direnv + +.antlr/ + +# ignore generated rust proto files +tierkreis-proto/proto_src + +_server_output.txt + +# Devenv (Nix) +.devenv* +devenv.local.nix + +cqcpython-secret.txt + + +*.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6f21ac3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2954 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 1.0.1", + "tower 0.5.1", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive", + "clap_lex", + "indexmap 1.9.3", + "once_cell", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.87", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.5.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.1", + "serde", +] + +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lasso" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8647c8a01e5f7878eacb2c323c4c949fdb63773110f0686c7810769874b7e0a" +dependencies = [ + "dashmap 4.0.2", + "hashbrown 0.11.2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opentelemetry" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570074cc999d1a58184080966e5bd3bf3a9a4af650c3b05047c2621e7405cd17" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-http" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6351496aeaa49d7c267fb480678d85d1cd30c5edb20b497c48c56f62a8c14b99" +dependencies = [ + "async-trait", + "bytes", + "http 1.1.0", + "opentelemetry", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e1f9c8b032d4f635c730c0efcf731d5e2530ea13fa8bef7939ddc8420696bd" +dependencies = [ + "async-trait", + "futures-core", + "http 1.1.0", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d3968ce3aefdcca5c27e3c4ea4391b37547726a70893aab52d3de95d5f8b34" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c627d9f4c9cdc1f21a29ee4bfbd6028fcb8bcf2a857b43f3abdf72c9c862f3" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "percent-encoding", + "rand", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.6.0", +] + +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.87", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.87", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "prost-types" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +dependencies = [ + "prost", +] + +[[package]] +name = "pyo3" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0220c44442c9b239dd4357aa856ac468a4f5e1f0df19ddb89b2522952eb4c6ca" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c819d397859445928609d0ec5afc2da5204e0d0f73d6bf9e153b04e83c9cdc2" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca882703ab55f54702d7bfe1189b41b0af10272389f04cae38fe4cd56c65f75f" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568749402955ad7be7bad9a09b8593851cd36e549ac90bfd44079cea500f3f21" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611f64e82d98f447787e82b8e7b0ebc681e1eb78fc1252668b2c605ffb4e1eb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.87", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.6.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "single" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79db6063472b62c5f6a206b3740b60bb78559df5c05236cae12354f904282fd" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-env-log" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877189d680101869f65ef94168105d6c188b3a143c13a2d42cf8a09c4c704f8a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "thiserror" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tierkreis-core" +version = "0.1.0" +dependencies = [ + "indenter", + "indexmap 1.9.3", + "itertools 0.10.5", + "lasso", + "once_cell", + "regex", + "rstest", + "single", + "thiserror", + "uuid", +] + +[[package]] +name = "tierkreis-proto" +version = "0.1.0" +dependencies = [ + "anyhow", + "http 1.1.0", + "indexmap 1.9.3", + "prost", + "prost-build", + "prost-types", + "rand", + "rstest", + "serde", + "thiserror", + "tierkreis-core", + "tonic", + "tonic-build", + "tracing", + "uuid", + "warp", +] + +[[package]] +name = "tierkreis-runtime" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "hyper-util", + "opentelemetry", + "opentelemetry-http", + "opentelemetry_sdk", + "rstest", + "serde", + "test-env-log", + "thiserror", + "tierkreis-core", + "tierkreis-proto", + "tokio", + "tokio-stream", + "tonic", + "tower 0.5.1", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "tierkreis-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "dashmap 5.5.3", + "futures", + "futures-core", + "http 1.1.0", + "hyper 1.5.0", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-otlp", + "opentelemetry_sdk", + "rand", + "serde", + "serde_json", + "serde_with", + "test-env-log", + "thiserror", + "tierkreis-core", + "tierkreis-proto", + "tierkreis-runtime", + "tokio", + "tonic", + "tonic-health", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "uuid", + "warp", +] + +[[package]] +name = "tierkreis-typecheck" +version = "0.0.0" +dependencies = [ + "prost", + "pyo3", + "tierkreis-core", + "tierkreis-proto", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tonic-health" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eaf34ddb812120f5c601162d5429933c9b527d901ab0e7f930d3147e33a09b2" +dependencies = [ + "async-stream", + "prost", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.6.0", + "pin-project-lite", + "slab", + "sync_wrapper 0.1.2", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc58af5d3f6c5811462cabb3289aec0093f7338e367e5a33d28c0433b3c7360b" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "chrono", + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[package]] +name = "url" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", + "rand", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper 0.14.31", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4eac9e3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ + +[workspace] +resolver = "2" + +members = [ + "tierkreis-core", + "tierkreis-proto", + "tierkreis-runtime", + "tierkreis-server", + "type_check", +] +[workspace.package] +rust-version = "1.75" +edition = "2021" +homepage = "https://github.com/CQCL/tierkreis" +repository = "https://github.com/CQCL/tierkreis" +license = "Apache-2.0" + +[workspace.dependencies] +prost = "0.13.3" +prost-build = "0.13.3" +prost-types = "0.13.3" +tonic = { version = "0.12.0", features = ["transport"] } +tonic-build = "0.12.0" +tonic-health = "0.12.0" +hyper = "1.5.0" +tower = "0.5.1" +tracing-opentelemetry = "0.27.0" +opentelemetry = "0.26.0" +opentelemetry-http = "0.26.0" +opentelemetry-otlp = "0.26.0" +opentelemetry_sdk = "0.26.0" +futures = "0.3.31" +futures-core = { version = "0.3.31", default-features = false } + +tracing = "0.1.4" +tracing-subscriber = "0.3.18" +http = "1.1.0" +serde = "1.0.197" +rstest = "0.21.0" + +[workspace.lints.rust] +missing_docs = "warn" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7411492 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM rust:latest as rustbuild + +COPY . . +RUN rustup component add rustfmt +RUN cargo build --release --package tierkreis-server +RUN apt-get update && apt-get install -y protobuf-compiler wget git +RUN GRPC_HEALTH_PROBE_VERSION=v0.3.1 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.4.5/grpc_health_probe-linux-386 && \ + chmod +x /bin/grpc_health_probe + +FROM debian:bookworm-slim + +WORKDIR /usr/local +RUN apt update && apt install -y openssl +COPY --from=rustbuild target/release/tierkreis-server /usr/local/bin/tierkreis-server + +EXPOSE 8080 9090 +# ENV TIERKREIS_HTTP_PORT 9090 +ENV TIERKREIS_GRPC_PORT 8080 +ENV TIERKREIS_HOST 0.0.0.0 + +ENTRYPOINT ["./bin/tierkreis-server"] diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..38f4899 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# tierkreis + +Quantum-classical hybrid workflow orchestration tool. +This is the top level repository containing the rust crates and python packages. + + +## Development +Requirements +- `cargo` >= 1.75 +- `uv` >= 0.4 + + +See the justfile (requires `just`) for common development commands. For example: + +```sh +just test_rust +``` + +## License + +This project is licensed under Apache License, Version 2.0 ([LICENSE][] or http://www.apache.org/licenses/LICENSE-2.0). + + + [LICENSE]: https://github.com/CQCL/tierkreis/blob/main/LICENCE diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..be0cc26 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,202 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1730890834, + "owner": "cachix", + "repo": "devenv", + "rev": "c5353d1a0483b8f0dc15933de91c6b1b9a892831", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1730874826, + "owner": "nix-community", + "repo": "fenix", + "rev": "8fa4ba339a0179efdb987663e743f49eed05c573", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1730831018, + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8c4dc69b9732f6bbe826b5fbb32184987520ff26", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-python": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1730716553, + "owner": "cachix", + "repo": "nixpkgs-python", + "rev": "8fcdb8ec34a1c2bae3f5326873a41b310e948ccc", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "nixpkgs-python", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1730741070, + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1730741070, + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat_2", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1730814269, + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "d70155fdc00df4628446352fc58adc640cd705c2", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "fenix": "fenix", + "nixpkgs": "nixpkgs", + "nixpkgs-python": "nixpkgs-python", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1730749868, + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "b51f9bc736dc0472481a47d7c05de2901323e543", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..61ff001 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,35 @@ +{ pkgs, lib, ... }: + +{ + + packages = [ + pkgs.protobuf3_21 + pkgs.just + pkgs.graphviz + ] ++ lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk; [ + frameworks.CoreServices + frameworks.CoreFoundation + frameworks.Security + frameworks.SystemConfiguration + ]); + + + # https://devenv.sh/languages/ + languages.python = { + enable = true; + version = "3.10"; + uv.enable = true; + }; + + languages.rust = { + channel = "stable"; + enable = true; + components = [ "rustc" "cargo" "clippy" "rustfmt" "rust-analyzer" ]; + }; + + # This allows building the type-check (pyo3) module on MacOSX "Apple Silicon" + enterShell = + if pkgs.stdenv.isDarwin && pkgs.stdenv.isAarch64 then '' + export RUSTFLAGS="$RUSTFLAGS -C link-arg=-undefined -C link-arg=dynamic_lookup" + '' else ''''; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..1438897 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,10 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable + nixpkgs-python: + url: github:cachix/nixpkgs-python + fenix: + url: github:nix-community/fenix + inputs: + nixpkgs: + follows: nixpkgs \ No newline at end of file diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 0000000..d3d29c8 --- /dev/null +++ b/integration/README.md @@ -0,0 +1 @@ +# Tierkreis Integration Tests \ No newline at end of file diff --git a/integration/pyproject.toml b/integration/pyproject.toml new file mode 100644 index 0000000..900a2b0 --- /dev/null +++ b/integration/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "integration-tests" +version = "0.1.0" +description = "Tierkries integration tests." +readme = "README.md" +requires-python = ">=3.10, <3.13" +dependencies = ["tierkreis"] + +[tool.ruff] +target-version = "py310" +# default + imports +lint.select = ["E4", "E7", "E9", "F", "I"] + +[tool.pyright] +include = ["."] +exclude = ["./venv"] +extraPaths = ["../python"] + +[tool.uv.sources] +tierkreis = { workspace = true } diff --git a/integration/tests/__init__.py b/integration/tests/__init__.py new file mode 100644 index 0000000..28912fa --- /dev/null +++ b/integration/tests/__init__.py @@ -0,0 +1,8 @@ +import os +from pathlib import Path + +LOCAL_SERVER_PATH = Path(__file__).parent / "../../target/debug/tierkreis-server" +release_tests: bool = os.getenv("TIERKREIS_RELEASE_TESTS") is not None +REASON = "TIERKREIS_RELEASE_TESTS is set." +PYTHON_TESTS_DIR = Path(__file__).parent.parent / "../python/tests" +PYTHON_WORKER = PYTHON_TESTS_DIR / "test_worker" diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py new file mode 100644 index 0000000..04cba9c --- /dev/null +++ b/integration/tests/conftest.py @@ -0,0 +1,121 @@ +import asyncio +import sys +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING, Any, AsyncIterator, Callable + +import pytest +from tierkreis.builder import Namespace +from tierkreis.client.runtime_client import RuntimeClient +from tierkreis.client.server_client import ServerRuntime +from tierkreis.core.signature import Signature +from tierkreis.pyruntime import PyRuntime + +from . import LOCAL_SERVER_PATH, PYTHON_TESTS_DIR, PYTHON_WORKER +from .managers.local_manager import local_runtime + +sys.path.append(str(PYTHON_TESTS_DIR)) +from test_worker import main # type: ignore # noqa: E402 + +if TYPE_CHECKING: + from grpclib.client import Channel + + +def pytest_addoption(parser): + parser.addoption( + "--docker", + action="store_true", + help="Whether to use docker container for server rather than local binary", + ) + parser.addoption( + "--server-logs", + action="store_true", + help="Whether to attempt to print server logs (for debugging).", + ) + + +@pytest.fixture(scope="session") +def event_loop(request): + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest.fixture(scope="session") +def pyruntime(): + return PyRuntime([main.root]) + + +@pytest.fixture(scope="session", params=[False, True]) +async def client(request, server_client, pyruntime) -> RuntimeClient: + # if parameter is true, return python runtime + if request.param: + return pyruntime + else: + return server_client + + +@pytest.fixture(scope="session") +async def server_client( + request, local_runtime_launcher +) -> AsyncIterator[ServerRuntime]: + isdocker = False + try: + isdocker = request.config.getoption("--docker") not in (None, False) + except Exception as _: + pass + if isdocker: + # launch docker container and close at end + from .managers.docker_manager import docker_runtime + + async with docker_runtime( + "cqc/tierkreis", + ) as local_client: + yield local_client + else: + # launch a local server for this test run and kill it at the end + async with local_runtime_launcher( + workers=[("python", PYTHON_WORKER)] + ) as client: + yield client + + +def _local_channel_launcher(request) -> Callable: + try: + logs = request.config.getoption("--server-logs") not in (None, False) + except Exception: + logs = False + + @asynccontextmanager + async def launch_with_overrides(**kwarg_overrides: Any) -> AsyncIterator["Channel"]: + kwargs = { + "workers": [], + "worker_uris": [], + "show_output": logs, + **kwarg_overrides, + } + async with local_runtime(LOCAL_SERVER_PATH, **kwargs) as channel: + yield channel + + return launch_with_overrides + + +@pytest.fixture(scope="session") +def local_runtime_launcher(request) -> Callable: + @asynccontextmanager + async def launch_with_overrides( + **kwarg_overrides: Any, + ) -> AsyncIterator[ServerRuntime]: + async with _local_channel_launcher(request)(**kwarg_overrides) as channel: + yield ServerRuntime(channel) + + return launch_with_overrides + + +@pytest.fixture() +async def sig(client: RuntimeClient) -> Signature: + return await client.get_signature() + + +@pytest.fixture() +def bi(sig: Signature) -> Namespace: + return Namespace(sig) diff --git a/integration/tests/managers/__init__.py b/integration/tests/managers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/integration/tests/managers/docker_manager.py b/integration/tests/managers/docker_manager.py new file mode 100644 index 0000000..b0e430b --- /dev/null +++ b/integration/tests/managers/docker_manager.py @@ -0,0 +1,208 @@ +"""Docker container management for tierkreis runtime servers and workers.""" + +import asyncio +import os +import socket +import subprocess +import sys +from contextlib import AsyncExitStack, asynccontextmanager, contextmanager +from pathlib import Path +from typing import ( + IO, + TYPE_CHECKING, + AsyncIterator, + Iterator, + List, + Optional, + Tuple, + ValuesView, + cast, +) + +from grpclib.client import Channel +from tierkreis.client.server_client import RuntimeLaunchFailed, ServerRuntime + +try: + from docker import DockerClient # type: ignore +except ImportError as e: + raise ImportError( + "docker not found," + " tierkreis may need to be installed with the 'docker' feature flag." + ) from e + +if TYPE_CHECKING: + from docker.models.containers import Container # type: ignore + from docker.models.networks import Network # type: ignore + + +def _write_process_out(logs: bytes) -> None: + with os.fdopen(sys.stdout.fileno(), "wb", closefd=False) as stdout: + stdout.write(logs) + stdout.flush() + + +def _get_free_port() -> str: + with socket.socket() as sock: + sock.bind(("", 0)) + return str(sock.getsockname()[1]) + + +class ManagedClient(DockerClient): + """DockerClient overload with container/network resource allocation context + managers.""" + + @contextmanager + def _run_container( + self, image: str, network_name: str, **kwargs + ) -> Iterator["Container"]: + cont = cast( + "Container", + self.containers.run( + image, detach=True, remove=True, network=network_name, **kwargs + ), + ) + try: + yield cont + + finally: + cont.stop() + + @contextmanager + def _docker_network(self, name: str = "tierkreis-net") -> Iterator[str]: + network = cast("Network", self.networks.create(name, driver="bridge")) + + try: + yield network.name or "" + + finally: + network.remove() + + +@asynccontextmanager +async def docker_runtime( + image: str, + worker_images: Optional[List[Tuple[str, str]]] = None, + host_workers: Optional[List[Path]] = None, + grpc_port: int = 8090, + show_output: bool = False, +) -> AsyncIterator[ServerRuntime]: + """Context manager for setting up a containerised runtime + workers and + return a connected client. + + :param image: name of tierkreis server image + :type image: str + :param worker_images: list of pairs of worker images and paths to worker + executables inside them, e.g. [("cqc/tierkreis-workers", + "/root/workers/pytket_worker"), ("cqc/tierkreis-workers", + "/root/workers/qermit_worker")], defaults to None + :type worker_images: Optional[List[Tuple[str, str]]], optional + :param host_workers: List of paths to local workers, e.g. + ["../workers/pytket_worker"], defaults to None + :type host_workers: Optional[List[Path]], optional + :param grpc_port: Port for server, defaults to 8090 + :type grpc_port: int, optional + :param show_output: Whether to print server logs on exit, defaults to False + :type show_output: bool, optional + :yield: RuntimeClient + :rtype: Iterator[AsyncIterator[RuntimeClient]] + """ + + worker_images = worker_images or [] + host_workers = host_workers or [] + container_to_host_ports = {"8080": str(grpc_port)} + + client = ManagedClient.from_env() + + # ExitStack will hold the exits for all the contexts used + # and if anything goes wrong in setup, everything done so far will be + # exited in reverse order + async with AsyncExitStack() as stack: + command: List[str] = [] + for path in host_workers: + port = await stack.enter_async_context( + _start_host_worker(container_to_host_ports.values(), path) + ) + command.extend(["--worker-remote", f"http://host.docker.internal:{port}"]) + + network_name = stack.enter_context(client._docker_network()) + + worker_coros = [] + for i, (worker_image, container_path) in enumerate(worker_images): + # worker needs given name for connection to work + container_name = f"worker_{i}" + work_cont = stack.enter_context( + client._run_container( + worker_image, + network_name, + name=container_name, + command=[ + container_path + "/main.py", + "--port", + "80", + ], + ) + ) + + worker_coros.append( + _check_start(work_cont, "Started worker server on port") + ) + command.extend(["--worker-remote", f"http://{container_name}:80"]) + + await asyncio.gather(*worker_coros) + proc_env = {} + + runtime_container = stack.enter_context( + client._run_container( + image, + network_name, + command=command, + ports=container_to_host_ports, + environment=proc_env, + ) + ) + + await _check_start(runtime_container, "Server started") + + async with Channel("localhost", grpc_port) as channel: + yield ServerRuntime(channel) + + if show_output: + _write_process_out(runtime_container.logs()) + + +async def _check_start(container: "Container", check_str: str): + start_lines = [] + for line in container.logs(stream=True): + start_lines.append(line) + if check_str in str(line): + # server is ready to receive requests + return + await asyncio.sleep(1) + + # failed to start correctly + _write_process_out(b"\n".join(start_lines)) + raise RuntimeLaunchFailed() + + +@asynccontextmanager +async def _start_host_worker( + ports_to_avoid: ValuesView[str], host_worker_path: Path +) -> AsyncIterator[str]: + while True: + free_port = _get_free_port() + if free_port not in ports_to_avoid: + break + proc = subprocess.Popen( + [f"{host_worker_path}/main.py", "--port", free_port], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + for line in cast(IO[bytes], proc.stdout): + # wait for server to finish starting and announce port + if free_port in str(line): + break + await asyncio.sleep(1) + try: + yield free_port + finally: + proc.terminate() diff --git a/integration/tests/managers/local_manager.py b/integration/tests/managers/local_manager.py new file mode 100644 index 0000000..b23f43a --- /dev/null +++ b/integration/tests/managers/local_manager.py @@ -0,0 +1,129 @@ +"""Context manager for a local tierkreis server +when the binary is available on the system.""" + +import asyncio +import json +import os +import signal +import subprocess +import sys +from contextlib import asynccontextmanager +from pathlib import Path +from threading import Thread +from typing import IO, Any, AsyncIterator, List, Optional, Union, cast + +from grpclib.client import Channel +from tierkreis.client.server_client import RuntimeLaunchFailed + + +def echo_thread(src: IO[bytes], dest: Union[int, str]): + def run(): + # closefd=False only works when 'open'ing a 'fileno()' + with open(dest, "wb", closefd=isinstance(dest, str)) as d: + for line in src: + d.write(line) + d.flush() + + t = Thread(target=run) + t.start() + return t + + +def _wait_for_print(proc_out: IO[bytes], content: str): + for line in proc_out: + if content in str(line): + break + + +@asynccontextmanager +async def local_runtime( + executable: Path, + workers: List[tuple[str, Path]], + worker_uris: List[tuple[str, str]], + port: int = 8080, + show_output: bool = False, + runtime_type_checking: Optional[str] = None, + job_server: bool = False, + checkpoint_endpoint: Optional[tuple[str, int]] = None, +) -> AsyncIterator[Channel]: + """Provide a context for a local runtime running in a subprocess. + + :param executable: Path to server binary + :type executable: Path + :param workers: List of Locations with Path of worker server + :type workers: List[str, Path] + :param worker_uris: List of Locations with Uri of remote worker + :type worker_uris: List[str, str] + :param port: Localhost grpc port, defaults to "8080" + :type port: str, optional + :param show_output: Show server tracing/errors, defaults to False + :type show_output: bool, optional + :param runtime_type_checking: Type checking mode, defaults to None + :type runtime_type_checking: str, optional + :param job_server: Whether to start as a job server, defaults to False + :type job_server: bool, optional + :param checkpoint_endpoint: Tuple of (host, port) for checkpoint server, + defaults to None + :type checkpoint_endpoint: tuple[str, int], optional + :yield: RuntimeClient + :rtype: Iterator[RuntimeClient] + """ + + command: List[Union[str, Path]] = [executable, "-c"] + + config: dict[str, Any] = {"job_server": job_server} + + config["worker_path"] = [{"location": x, "path": str(y)} for x, y in workers] + config["worker_uri"] = [{"location": x, "uri": str(y)} for x, y in worker_uris] + + if port: + config["port"] = port + + if runtime_type_checking: + config["runtime_type_checking"] = runtime_type_checking + + if checkpoint_endpoint: + config["checkpoint_endpoint"] = checkpoint_endpoint + assert job_server + + command.append(str(json.dumps(config))) + proc = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE if show_output else subprocess.DEVNULL, + ) + + echo_threads = [] + if show_output: + echo_threads.append( + echo_thread(cast(IO[bytes], proc.stderr), sys.stderr.fileno()) + ) + + proc_out = cast(IO[bytes], proc.stdout) + _wait_for_print(proc_out, "Server started") + # We opened stdout as a subprocess.PIPE, so we must read it + # to prevent the buffer from filling up (which blocks the server) + echo_threads.append( + echo_thread(proc_out, sys.stdout.fileno() if show_output else os.devnull) + ) + + if proc.poll() is not None: + # process has terminated unexpectedly + for t in echo_threads: + t.join() + raise RuntimeLaunchFailed() + + try: + async with Channel("localhost", port) as channel: + yield channel + + finally: + proc.send_signal(signal.SIGINT) + + await asyncio.sleep(1) # FIXME deadlocks without this line (?) + proc.kill() + + if show_output: + # Ensure that output has been echoed (and wait for server to close stream) + for t in echo_threads: + t.join() diff --git a/integration/tests/test_cli.py b/integration/tests/test_cli.py new file mode 100644 index 0000000..b6228c6 --- /dev/null +++ b/integration/tests/test_cli.py @@ -0,0 +1,108 @@ +import subprocess +import tempfile +from typing import TYPE_CHECKING + +import pytest +import tierkreis.core.protos.tierkreis.v1alpha1.graph as pg +from tierkreis.core.values import IntValue, StructValue, VecValue +from utils import nint_adder # type: ignore + +if TYPE_CHECKING: + from tierkreis.client import ServerRuntime + + +@pytest.mark.asyncio +def test_signature(server_client: "ServerRuntime"): + host, port = server_client.socket_address().split(":") + command = ["tkrs", "-r", host, "-p", port, "signature"] + p = subprocess.run(command, capture_output=True) + print(p.stderr) + assert p.returncode == 0 + + +@pytest.mark.asyncio +def test_view(server_client: "ServerRuntime"): + host, port = server_client.socket_address().split(":") + with tempfile.TemporaryDirectory() as tmpdirname: + nint_adder_file_2 = tmpdirname + "/nint_adder_2.bin" + with open(nint_adder_file_2, "wb") as f: + f.write(bytes(nint_adder(2).to_proto())) + command = [ + "tkrs", + "-r", + host, + "-p", + port, + "view", + str(nint_adder_file_2), + tmpdirname + "test_tierkreis_proto.pdf", + ] + assert subprocess.call(command) == 0 + + +@pytest.mark.asyncio +def test_run_with_args(server_client: "ServerRuntime"): + host, port = server_client.socket_address().split(":") + command_header = ["tkrs", "-r", host, "-p", port, "run"] + with tempfile.TemporaryDirectory() as tmpdirname: + nint_adder_file_2 = tmpdirname + "/nint_adder_2.bin" + with open(nint_adder_file_2, "wb") as f: + f.write(bytes(nint_adder(2).to_proto())) + v: StructValue = StructValue( + { + "array": VecValue(values=[IntValue(3), IntValue(5), IntValue(11)]), + } + ) + fname = tmpdirname + "/test1.bin" + with open(fname, "wb") as f: + f.write(bytes(pg.StructValue(map=v.to_proto_dict()))) + output = subprocess.check_output(command_header + [nint_adder_file_2, fname]) + assert output.decode("utf-8").strip() == "out: 16" + + nint_adder_file_4 = tmpdirname + "/nint_adder_4.bin" + with open(nint_adder_file_4, "wb") as f: + f.write(bytes(nint_adder(4).to_proto())) + v = StructValue( + { + "array": VecValue( + values=[IntValue(1), IntValue(3), IntValue(5), IntValue(11)] + ), + } + ) + fname = tmpdirname + "/test2.bin" + with open(fname, "wb") as f: + f.write(bytes(pg.StructValue(map=v.to_proto_dict()))) + output = subprocess.check_output(command_header + [nint_adder_file_4, fname]) + assert output.decode("utf-8").strip() == "out: 20" + + v = StructValue({"array_wrong_name": VecValue(values=[])}) + fname = tmpdirname + "/test4.bin" + with open(fname, "wb") as f: + f.write(bytes(pg.StructValue(map=v.to_proto_dict()))) + p = subprocess.run( + command_header + [nint_adder_file_2, fname], capture_output=True + ) + assert p.returncode != 0 + assert "Expected type" in p.stderr.decode("utf-8") + + +@pytest.mark.asyncio +def test_run_with_py_args(server_client: "ServerRuntime"): + host, port = server_client.socket_address().split(":") + command_header = ["tkrs", "-r", host, "-p", port, "run"] + with tempfile.TemporaryDirectory() as tmpdirname: + nint_adder_file_2 = tmpdirname + "/nint_adder_2.bin" + with open(nint_adder_file_2, "wb") as f: + f.write(bytes(nint_adder(2).to_proto())) + output = subprocess.check_output( + command_header + [nint_adder_file_2, "--py-inputs", '{"array": [3, 5, 11]}'] + ) + assert output.decode("utf-8").strip() == "out: 16" + + p = subprocess.run( + command_header + + [nint_adder_file_2, "--py-inputs", '{"wrong_name": [3, 5, 11]}'], + capture_output=True, + ) + assert p.returncode != 0 + assert "Expected type" in p.stderr.decode("utf-8") diff --git a/integration/tests/test_scoping.py b/integration/tests/test_scoping.py new file mode 100644 index 0000000..dc3ff96 --- /dev/null +++ b/integration/tests/test_scoping.py @@ -0,0 +1,260 @@ +from typing import AsyncIterator + +import pytest +from tierkreis.builder import Const, Copyable, Output, Scope, ValueSource, graph +from tierkreis.client import RuntimeClient, ServerRuntime + +from . import PYTHON_WORKER + +# This avoids errors on every call to a decorated _GraphDef + + +@pytest.mark.asyncio +async def test_run_scoped_program(bi, client: RuntimeClient) -> None: + @graph() + def g() -> Output: + a = Const(3) + with Scope(): + b = Copyable(Const(2)) + with Scope(): + c = bi.iadd(a, b) + d = bi.iadd(c, b) + e = bi.iadd(d, Const(1)) + return Output(value=e) + + outputs = await client.run_graph(g) + assert outputs["value"].try_autopython() == 8 + + +@pytest.fixture(scope="session") +async def outer_server_client( + local_runtime_launcher, server_client +) -> AsyncIterator[ServerRuntime]: + async with local_runtime_launcher( + port=9090, + worker_uris=[("inner", "http://" + server_client.socket_address())], + ) as outer: + yield outer + + +@pytest.mark.asyncio +async def test_remote_scopes(outer_server_client, bi): + @graph() + def g() -> Output: + a = Copyable(Const(3)) + with Scope("inner"): + b = Const(2) + c = bi.iadd(b, a) + d = bi.iadd(a, c) + return Output(value=d) + + outputs = await outer_server_client.run_graph(g) + assert outputs["value"].try_autopython() == 8 + + +@pytest.mark.asyncio +async def test_remote_scopes_escape_hatch(local_runtime_launcher, bi): + # Here the inner runtime does NOT have a python worker, but the outer does + async with local_runtime_launcher( + port=8081, + ) as inner: + async with local_runtime_launcher( + port=9091, + worker_uris=[("inner", "http://" + inner.socket_address())], + workers=[("python", PYTHON_WORKER)], + ) as outer: + + @graph() + def g() -> Output: + with Scope("inner"): + o = bi["python_nodes"].id_py(Const(1)) + return Output(out=o) + + res = await outer.run_graph(g) + assert res["out"].try_autopython() == 1 + + +@pytest.mark.asyncio +async def test_escape_hatch_preserves_callback(local_runtime_launcher, bi): + # Here only the outer runtime has a python worker, neither middle/inner + async with local_runtime_launcher( + port=8081, + ) as inner: + async with local_runtime_launcher( + port=9081, worker_uris=[("inner", "http://" + inner.socket_address())] + ) as middle: + async with local_runtime_launcher( + port=9091, + worker_uris=[("middle", "http://" + middle.socket_address())], + workers=[("python", PYTHON_WORKER)], + ) as outer: + + @graph() + def g_middle_only(value: ValueSource) -> Output: + with Scope("inner"): + b = Const(4) + r = bi.iadd(value, b) + return Output(value=r) + + res = await middle.run_graph(g_middle_only, value=3) + assert res["value"].try_autopython() == 7 + with pytest.raises(RuntimeError, match="Could not find location inner"): + await outer.run_graph(g_middle_only, value=3) + + # Quick sanity check that we can run middle in that scope + @graph() + def run_middle_from_outer() -> Output: + with Scope("middle"): + r = bi.eval(Const(g_middle_only), value=Const(5)) + return Output(out=r) + + res = await outer.run_graph(run_middle_from_outer) + assert res["out"].try_autopython() == 9 + + # Now try and run middle in that scope via escape hatch and callback + @graph() + def use_escape_hatch(arg: ValueSource) -> Output: + with Scope("middle"): + # do_callback runs on the python worker, attached to . + # The callback needs to execute the graph *in this Scope*, + # i.e. on the middle runtime. (Note: a more complex case would + # attach the python worker to a second runtime child of , + # that might be even harder.) + r = bi["python_nodes"].do_callback( + graph=Const(g_middle_only), value=arg + ) + return Output(outp=r) + + res = await outer.run_graph(use_escape_hatch, arg=7) + assert res["outp"].try_autopython() == 11 + + +@pytest.mark.asyncio +async def test_double_remote_scopes_escape_hatch(local_runtime_launcher, bi): + async with local_runtime_launcher(port=8081) as inner: + async with local_runtime_launcher( + port=9092, + worker_uris=[("inner", "http://" + inner.socket_address())], + ) as middle: + async with local_runtime_launcher( + port=9093, + worker_uris=[("middle", "http://" + middle.socket_address())], + workers=[("python", PYTHON_WORKER)], + ) as outer: + + @graph() + def g2() -> Output: + with Scope("middle"): + with Scope("inner"): + o = bi["python_nodes"].id_py(Const(2)) + return Output(out=o) + + res = await outer.run_graph(g2) + assert res["out"].try_autopython() == 2 + + +# TODO we now need some test that the stuff in the scope +# is actually running remotely. + + +@pytest.mark.asyncio +async def test_worker_scopes(server_client: ServerRuntime, bi): + @graph() + def g() -> Output: + with Scope("python"): + x = bi["python_nodes"].id_py(Const(1)) + return Output(value=x) + + outputs = await server_client.run_graph(g) + assert outputs["value"].try_autopython() == 1 + + +@pytest.mark.asyncio +async def test_double_scope(server_client: ServerRuntime, bi, local_runtime_launcher): + async with local_runtime_launcher( + port=8081, + worker_uris=[("inner", "http://" + server_client.socket_address())], + ) as inner: + async with local_runtime_launcher( + port=9091, + worker_uris=[("inner", "http://" + inner.socket_address())], + ) as outer: + + @graph() + def g() -> Output: + with Scope("inner/inner"): + with Scope("python"): + x = bi["python_nodes"].id_py(Const(1)) + return Output(value=x) + + outputs = await outer.run_graph(g) + assert outputs["value"].try_autopython() == 1 + + +@pytest.mark.asyncio +async def test_nested_callback(outer_server_client: ServerRuntime, bi): + @graph() + def g_only_on_outer(value: ValueSource) -> Output: + # Only the outer runtime has a scope called "Inner" + with Scope("inner"): + a = bi["python_nodes"].id_py(value) + return Output(value=a) + + @graph() + def g_that_calls_back(in_graph: ValueSource, in_value: ValueSource) -> Output: + # do_callback must run on the python worker attached to the inner + # runtime, but when it runs it's in_graph argument, + # that must callback all the way to the *outer* runtime. + a = bi["python_nodes"].do_callback(graph=in_graph, value=in_value) + return Output(out=a) + + out = await outer_server_client.run_graph( + g_that_calls_back, in_value=3, in_graph=g_only_on_outer + ) + assert out["out"].try_autopython() == 3 + + +@pytest.mark.asyncio +async def test_callback_inside_scope(outer_server_client: ServerRuntime, bi): + @graph() + def g_only_on_outer(value: ValueSource) -> Output: + # This graph will only run on the outer runtime, + # because only that has a subscope called "Inner" + with Scope("inner"): + a = bi["python_nodes"].id_py(value) + return Output(value=a) + + @graph() + def g_anywhere(value: ValueSource): # This will run anywhere + a = bi["python_nodes"].id_py(value) + return Output(value=a) + + @graph() + def g_that_calls_back(in_graph: ValueSource, in_value: ValueSource) -> Output: + with Scope("inner"): + # do_callback should only call back to this *inner* runtime + a = bi["python_nodes"].do_callback(graph=in_graph, value=in_value) + return Output(out=a) + + # Sanity check that the graphs are valid + out = await outer_server_client.run_graph(g_only_on_outer, value=3) + assert out["value"].try_autopython() == 3 + out = await outer_server_client.run_graph(g_anywhere, value=4) + assert out["value"].try_autopython() == 4 + out = await outer_server_client.run_graph( + g_that_calls_back, in_graph=g_anywhere, in_value=5 + ) + assert out["out"].try_autopython() == 5 + expected_err_regex = ( + r"failed to run function in worker\n" + # The next is from the run_graph callback made by the worker + r".*Run_graph execution failed with message:" + # And this is the error from inside the graph run by the callback + r".*Could not find location inner to run box" + ) + with pytest.raises(RuntimeError, match=expected_err_regex): + out = await outer_server_client.run_graph( + g_that_calls_back, in_graph=g_only_on_outer, in_value=6 + ) + # Graph ran -> callback went all the way back to outer scope + pytest.fail("Graph ran, so callback escaped user's explicit scope") diff --git a/integration/tests/test_server.py b/integration/tests/test_server.py new file mode 100644 index 0000000..f870927 --- /dev/null +++ b/integration/tests/test_server.py @@ -0,0 +1,236 @@ +import base64 + +import pytest +from sample_graph import sample_graph as sample_g # type: ignore +from tierkreis import TierkreisGraph +from tierkreis.builder import ValueSource +from tierkreis.client import ServerRuntime +from tierkreis.core import Labels +from tierkreis.core.function import FunctionName +from tierkreis.core.protos.tierkreis.v1alpha1.controller import NodeId +from tierkreis.core.tierkreis_graph import ( + BoxNode, + ConstNode, + FunctionNode, + GraphValue, +) +from tierkreis.core.type_errors import TierkreisTypeErrors +from tierkreis.core.values import ( + IntValue, + TierkreisValue, + VariantValue, +) +from tierkreis.pyruntime import PyRuntime, python_builtin + +from . import REASON, release_tests + + +@pytest.mark.asyncio +async def test_mistyped_op(server_client: ServerRuntime): + tk_g = TierkreisGraph() + nod = tk_g.add_func("python_nodes::mistyped_op", inp=tk_g.input["testinp"]) + tk_g.set_outputs(out=nod) + with pytest.raises( + RuntimeError, + match=r"Internal Server Error", + ): + await server_client.run_graph(tk_g, testinp=3) + + +@pytest.mark.asyncio +async def test_infer_errors_when_running(server_client: ServerRuntime) -> None: + # build graph with two type errors + tg = TierkreisGraph() + node_0 = tg.add_const(0) + node_1 = tg.add_const(1) + tg.add_edge(node_0["value"], tg.input["illegal"]) + tg.set_outputs(out=node_1["invalid"]) + + with pytest.raises(TierkreisTypeErrors) as err: + await server_client.run_graph(tg) + + assert len(err.value) == 2 + + +def id_graph() -> TierkreisGraph: + tg = TierkreisGraph() + tg.set_outputs(value=tg.add_func("python_nodes::id_py", value=tg.input["value"])) + return tg + + +@pytest.mark.asyncio +@pytest.mark.skipif(release_tests, reason=REASON) +async def test_runtime_worker( + server_client: ServerRuntime, local_runtime_launcher +) -> None: + bar = local_runtime_launcher( + port=9091, + worker_uris=[("inner", "http://" + server_client.socket_address())], + # make sure it has to talk to the other server for the test worker functions + ) + async with bar as runtime_server: + await runtime_server.run_graph(id_graph(), value=1) + + +@pytest.mark.asyncio +async def test_callback(server_client: ServerRuntime): + tg = TierkreisGraph() + idnode = tg.add_func("python_nodes::id_with_callback", value=tg.add_const(2)) + tg.set_outputs(out=idnode) + + assert (await server_client.run_graph(tg))["out"].try_autopython() == 2 + + +@pytest.mark.asyncio +async def test_do_callback(server_client: ServerRuntime): + tk_g = TierkreisGraph() + tk_g.set_outputs(value=tk_g.input["value"]) + + tk_g2 = TierkreisGraph() + callbacknode = tk_g2.add_func( + "python_nodes::do_callback", + graph=tk_g2.input["in_graph"], + value=tk_g2.input["in_value"], + ) + tk_g2.set_outputs(out=callbacknode["value"]) + out = await server_client.run_graph(tk_g2, in_value=3, in_graph=tk_g) + assert out["out"].try_autopython() == 3 + + +@pytest.mark.asyncio +async def test_reports_error(server_client: ServerRuntime): + pow_g = TierkreisGraph() + pow_g.set_outputs( + value=pow_g.add_tag( + Labels.BREAK, + value=pow_g.add_func("ipow", a=pow_g.add_const(2), b=pow_g.input["value"]), + ) + ) + loop_g = TierkreisGraph() + loop_g.set_outputs( + value=loop_g.add_func( + "loop", body=loop_g.add_const(pow_g), value=loop_g.input["value"] + ) + ) + + # Sanity check the graph does execute ipow + out = await server_client.run_graph(loop_g, value=0) + assert out == {"value": IntValue(1)} + expected_err_msg = "Input b to ipow must be positive integer" + with pytest.raises(RuntimeError, match=expected_err_msg): + await server_client.run_graph(loop_g, value=-1) + + +@pytest.fixture +def sample_graph(): + return sample_g() + + +@pytest.mark.asyncio +async def test_run_graph( + server_client: ServerRuntime, sample_graph: TierkreisGraph, pyruntime: PyRuntime +): + ins = {"inp": "hello", "vv": VariantValue("one", TierkreisValue.from_python(1))} + out_py = await pyruntime.run_graph( + sample_graph, + **ins, + ) + + out_rs = await server_client.run_graph(sample_graph, **ins) + assert out_rs == out_py + + +@pytest.mark.asyncio +async def test_builtin_signature(server_client: ServerRuntime): + # TODO test all the implementations as well! + remote_ns = (await server_client.get_signature()).root.functions + assert remote_ns.keys() == python_builtin.namespace.functions.keys() + for f, tkfunc in python_builtin.namespace.functions.items(): + remote_func = remote_ns[f] + assert remote_func == tkfunc.declaration + + +@pytest.mark.asyncio +async def test_stack_trace(server_client: ServerRuntime, pyruntime: PyRuntime): + from tierkreis.builder import ( + Break, + Const, + Continue, + Else, + If, + IfElse, + MakeTuple, + Namespace, + Output, + UnpackTuple, + graph, + loop, + ) + + bi = Namespace(await server_client.get_signature()) + + @graph() + def loop_graph( + value: ValueSource, + ) -> Output: + @loop() + def loop_def(ins_and_outs): + labels, traces = UnpackTuple(ins_and_outs, 2) + rest, label = bi.pop(labels) + rest1, rest2 = bi.copy(rest) + ntraces = bi.push(traces, bi["python_nodes"].dump_stack(label)) + with IfElse(bi.eq(rest1, Const([]))) as test: + with If(): + Break(ntraces) + with Else(): + Continue(MakeTuple(rest2, ntraces)) + return Output(test.nref) + + return Output(loop_def(value)) + + @graph() + def outer_graph( + value: ValueSource, + ) -> Output: + return Output(loop_graph(MakeTuple(value, Const([])))) + + (box_node,) = [ + n for n in range(outer_graph.n_nodes) if isinstance(outer_graph[n], BoxNode) + ] + + (loop_node,) = [ + n + for n in range(loop_graph.n_nodes) + if isinstance(nod := loop_graph[n], FunctionNode) + and nod.function_name == FunctionName("loop") + ] + (stack_trace_node,) = [ + inner_idx + for n in loop_graph.nodes() + if isinstance(n, ConstNode) and isinstance(n.value, GraphValue) + for inner_idx in range(n.value.value.n_nodes) + if isinstance(inner_node := n.value.value[inner_idx], FunctionNode) + and inner_node.function_name == FunctionName.parse("python_nodes::dump_stack") + ] + server_results = await server_client.run_graph( + outer_graph, value=["foo", "bar", "baz"] + ) + + def loop_iter_id(iter: int) -> NodeId: + nid = NodeId() + nid.node_index = stack_trace_node + nid.prefix = [f"N{box_node}", f"N{loop_node}", f"L{iter}"] + return nid + + stack_traces = [ + base64.b64encode(bytes(loop_iter_id(iter))).decode("ascii") + for iter in [1, 2, 3] + ] + printed_traces = [ + f"{label} {trace}" for label, trace in zip(["baz", "bar", "foo"], stack_traces) + ] + assert server_results["value"].try_autopython() == printed_traces + + pyresults = await pyruntime.run_graph(outer_graph, value=["foo", "bar", "baz"]) + # stack traces are not provided by the python Runtime + assert pyresults["value"].try_autopython() == ["baz ", "bar ", "foo "] diff --git a/justfile b/justfile new file mode 100644 index 0000000..ec4f399 --- /dev/null +++ b/justfile @@ -0,0 +1,30 @@ +uvrun := "uv run" + +setup: + uv sync --all-extras + +default: + @just --list + +test_rust: + {{uvrun}} cargo test --workspace --all-features + +test_python: + {{uvrun}} pytest python + cd python && {{uvrun}} ../.github/workflows/run_with_server ../target/debug/tierkreis-server ../.github/workflows/pr_server_config.json pytest --host=localhost --port=8090 --client-only + +fix_rust: + cargo clippy --fix --allow-dirty --allow-staged --workspace --all-features + +lint: + {{uvrun}} ruff format --check + {{uvrun}} ruff check + cd python && {{uvrun}} pyright + cargo fmt --check + cargo clippy + +fix_python: + {{uvrun}} ruff check --fix + +build: + cargo build \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..82feb2b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[tool.uv.workspace] +members = ["python", "integration", "type_check"] + +[dependency-groups] +# Default dev dependency group +dev = [ + { include-group = "lint" }, + { include-group = "test" }, + { include-group = "docs" }, +] +lint = [ + "ruff~=0.5", + "pyright==1.1.373", +] +test = [ + "pytest>=6.2,<9", + "pytest-asyncio>=0.16,<0.17", + "pytest-cov>=5.0,<6", + "pydantic>=2.9.2", +] +docs = ["sphinx>=4.3", "sphinx-book-theme>=1.1.2"] + +[tool.uv] +extra-index-url = [ + "https://quantinuumsw.jfrog.io/artifactory/api/pypi/pypi_local/simple", +] diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..61e9164 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,2 @@ +./venv +tierkreis/core/protos/ diff --git a/python/README.md b/python/README.md index 4342078..5f44737 100644 --- a/python/README.md +++ b/python/README.md @@ -9,7 +9,7 @@ For a detailed introduction read the paper: This repository contains the source for the `tierkreis` python package, and the protocol buffer definitions for Tierkreis data types and gRPC services (in the `protos` directory) which define the protocols for the system. -The python package provides a complete development and testing evironment for writing and running Tierkreis program, and allows you to write extensions ("workers") in python. By implementing the gRPC services you can also implement runtimes and workers in other languages. +The python package provides a complete development and testing environment for writing and running Tierkreis program, and allows you to write extensions ("workers") in python. By implementing the gRPC services you can also implement runtimes and workers in other languages. ## Getting Started @@ -71,8 +71,7 @@ render_graph(sum_pair, "filename", "pdf") ![sum_pair graph](https://user-images.githubusercontent.com/12997250/199997054-8cc815e2-39d3-4a9c-95d0-411510cb5465.svg ) ### Type check - -If you have the `typecheck` extension installed, you can replace `@graph` with `@graph(type_check_sig=sig)`, providing the signature retrived from the client as above, and the graph will be type checked when you call the building function. A graph is well-typed if type annotations can be inferred for every edge of the graph. If type check fails, the error message will try to indicate the location of your error. +If you have the `typecheck` extension installed, you can replace `@graph` with `@graph(type_check_sig=sig)`, providing the signature retrieved from the client as above, and the graph will be type checked when you call the building function. A graph is well-typed if type annotations can be inferred for every edge of the graph. If type check fails, the error message will try to indicate the location of your error. The type checked version of the graph above looks like: @@ -107,7 +106,6 @@ For a more involved example see [variational.ipynb](examples/variational.ipynb) ## Custom workers _Workers_ are standalone servers which implement a set of functions which can connect to a Tierkreis runtime to add extra primitives. - They do this by implementing the `worker` gRPC service. The `tierkreis` python package makes it easy to do this by taking care of all the server logic, and the conversion to and from Tierkreis data types. Note that workers are intended to be deployed as part of remote Tierkreis runtimes, but we can use the PyRuntime to test and develop them without any networking code. For example, we could define a custom function to sum a list: diff --git a/python/examples/variational-viz.ipynb b/python/examples/variational-viz.ipynb index 46a1db8..5f1cc08 100644 --- a/python/examples/variational-viz.ipynb +++ b/python/examples/variational-viz.ipynb @@ -51,7 +51,7 @@ "from tierkreis.pyruntime.python_runtime import VizRuntime\n", "import sys\n", "from pytket import Circuit\n", - "from sympy import symbols\n" + "from sympy import symbols" ] }, { @@ -80,13 +80,16 @@ "# sc22_worker is already in same directory\n", "import sc22_worker.main\n", "\n", - "cl = VizRuntime(\"http://localhost:3000\", [test_worker.main.root, pytket_worker.main.root, sc22_worker.main.root])\n", + "cl = VizRuntime(\n", + " \"http://localhost:3000\",\n", + " [test_worker.main.root, pytket_worker.main.root, sc22_worker.main.root],\n", + ")\n", "\n", "sig = await cl.get_signature()\n", "bi = Namespace(sig)\n", "pt = bi[\"pytket\"]\n", "pn = bi[\"python_nodes\"]\n", - "sc = bi[\"sc22\"]\n" + "sc = bi[\"sc22\"]" ] }, { @@ -191,7 +194,7 @@ "ansatz.measure_all()\n", "\n", "\n", - "render_circuit_jupyter(ansatz)\n" + "render_circuit_jupyter(ansatz)" ] }, { @@ -251,7 +254,7 @@ " return Output(push_pair(Const([]), init_params, init_score))\n", "\n", "\n", - "cl.viz_graph(initial)\n" + "cl.viz_graph(initial)" ] }, { @@ -333,6 +336,7 @@ " # run loop with initial value\n", " return Output(loop_def(init_val))\n", "\n", + "\n", "main = main.inline_boxes(True)\n", "cl.viz_graph(main)" ] @@ -353,7 +357,7 @@ "res = await cl.run_viz_graph(main)\n", "\n", "# print final parameters and score\n", - "res[\"value\"].try_autopython()[-1]\n" + "res[\"value\"].try_autopython()[-1]" ] } ], diff --git a/python/examples/variational.ipynb b/python/examples/variational.ipynb index 2bb5c42..bc17b2d 100644 --- a/python/examples/variational.ipynb +++ b/python/examples/variational.ipynb @@ -48,7 +48,7 @@ "from tierkreis.pyruntime import PyRuntime\n", "import sys\n", "from pytket import Circuit\n", - "from sympy import symbols\n" + "from sympy import symbols" ] }, { @@ -83,7 +83,7 @@ "bi = Namespace(sig)\n", "pt = bi[\"pytket\"]\n", "pn = bi[\"python_nodes\"]\n", - "sc = bi[\"sc22\"]\n" + "sc = bi[\"sc22\"]" ] }, { @@ -166,7 +166,7 @@ "ansatz.measure_all()\n", "\n", "\n", - "render_circuit_jupyter(ansatz)\n" + "render_circuit_jupyter(ansatz)" ] }, { @@ -309,7 +309,7 @@ " return Output(bi.push(lst, pair))\n", "\n", "\n", - "push_pair\n" + "push_pair" ] }, { @@ -476,7 +476,7 @@ " return Output(push_pair(Const([]), init_params, init_score))\n", "\n", "\n", - "initial\n" + "initial" ] }, { @@ -621,7 +621,7 @@ " return Output(bi.fdiv(y, Const(2.0)))\n", "\n", "\n", - "zexp_to_parity\n" + "zexp_to_parity" ] }, { @@ -923,7 +923,7 @@ "res = await cl.run_graph(main.graph)\n", "\n", "# print final parameters and score\n", - "res[\"value\"].try_autopython()[-1]\n" + "res[\"value\"].try_autopython()[-1]" ] }, { diff --git a/python/examples/viz_demo.ipynb b/python/examples/viz_demo.ipynb index b5ccba2..57380c5 100644 --- a/python/examples/viz_demo.ipynb +++ b/python/examples/viz_demo.ipynb @@ -15,7 +15,7 @@ "sys.path.append(str(_TK_ROOT_DIR / \"tests/\"))\n", "import test_worker.main\n", "\n", - "# Start a local tierkreis-viz instance and provide the port number \n", + "# Start a local tierkreis-viz instance and provide the port number\n", "# in the url as \"http://localhost:\"\n", "cl = VizRuntime(\"http://localhost:3000\", [test_worker.main.root])\n", "# cl = PyRuntime([test_worker.main.root])\n", @@ -25,7 +25,7 @@ "pn = bi[\"python_nodes\"]\n", "\n", "\n", - "delay = lambda : Const(5)" + "delay = lambda: Const(5)" ] }, { @@ -38,7 +38,10 @@ "def arithmetic_graph(xvec) -> Output:\n", " xvec, x = bi.pop(pn.id_delay(delay(), xvec))\n", " _, y = bi.pop(pn.id_delay(delay(), xvec))\n", - " return Output(bi.iadd(bi.fmul(pn.id_delay(delay(), x), Const(3)), pn.id_delay(delay(), y)))\n", + " return Output(\n", + " bi.iadd(bi.fmul(pn.id_delay(delay(), x), Const(3)), pn.id_delay(delay(), y))\n", + " )\n", + "\n", "\n", "cl.viz_graph(arithmetic_graph)" ] @@ -75,7 +78,8 @@ " e = bi.iadd(d, Const(1))\n", " return Output(value=e)\n", "\n", - "_ = await cl.type_check_graph(boxed_g)\n" + "\n", + "_ = await cl.type_check_graph(boxed_g)" ] } ], diff --git a/python/protos/v1alpha1/controller/runtime_storage_api.proto b/python/protos/v1alpha1/controller/runtime_storage_api.proto new file mode 100644 index 0000000..056ae7f --- /dev/null +++ b/python/protos/v1alpha1/controller/runtime_storage_api.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package tierkreis.v1alpha1.controller; + +import "v1alpha1/controller/tierkreis.proto"; + + +// This service is only used internally Tierkreis runtime +service CheckpointRecordingService { + rpc RecordOutput(RecordOutputRequest) returns (RecordOutputResponse); + rpc RecordJobFinished(RecordJobFinishedRequest) returns (RecordJobFinishedResponse); + rpc RecordNodeRun(RecordNodeRunRequest) returns (RecordNodeRunResponse); + rpc RecordNodeFinished(RecordNodeFinishedRequest) returns (RecordNodeFinishedResponse); +} + +message RecordOutputRequest { + string label = 1; + bytes value = 2; + + // Relationship fields. + string job_id = 101; +} +message RecordOutputResponse {} + +message RecordJobFinishedRequest { + string job_id = 1; + optional string error_message = 2; +} +message RecordJobFinishedResponse {} + +message RecordNodeRunRequest { + NodeId id = 1; + uint32 attempt_id = 2; + optional uint32 expected_duration_sec = 3; + + // Relationship fields. + string job_id = 101; +} +message RecordNodeRunResponse {} + +message RecordNodeFinishedRequest { + NodeId id = 1; + uint32 attempt_id = 2; + bytes outputs = 3; + + // Relationship fields. + string job_id = 101; +} +message RecordNodeFinishedResponse {} diff --git a/python/protos/v1alpha1/controller/tierkreis.proto b/python/protos/v1alpha1/controller/tierkreis.proto new file mode 100644 index 0000000..5d7d0e9 --- /dev/null +++ b/python/protos/v1alpha1/controller/tierkreis.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package tierkreis.v1alpha1.controller; + +import "google/protobuf/timestamp.proto"; + +message Graph { + string id = 1; // note: not a Job ID, because multiple jobs may share a graph + + bytes definition = 101; +} + +message NodeId { + repeated string prefix = 1; // example: ["N1", "L3", "N2", "M1"] + uint32 node_index = 2; +} + +message NodeStatus { + NodeId id = 1; + string job_id = 2; + optional uint32 expected_duration_sec = 3; + + uint32 retry_count = 100; + + optional google.protobuf.Timestamp finished_at = 201; +} diff --git a/python/protos/v1alpha1/graph.proto b/python/protos/v1alpha1/graph.proto index 45d6b35..cade8a5 100644 --- a/python/protos/v1alpha1/graph.proto +++ b/python/protos/v1alpha1/graph.proto @@ -1,148 +1,294 @@ syntax = "proto3"; +// The core Tierkreis language of `Graph`s, `Value`s and `Type`s. package tierkreis.v1alpha1.graph; +// Structured name identifying a path from a root to a Runtime message Location { + // Atoms in the name, each identifying a Runtime that is a child + // of the previous Runtime repeated string location = 1; } +// A `Value::struct` of unordered fields named by distinct strings message StructValue { + // Fields keyed by name map map = 1; } +// Contents of a `Value::pair` or one entry in a `MapValue`, i.e. two values message PairValue { + // First element of the `Value::pair` or key of the map entry Value first = 1; + + // Second element of the `Value::pair` or value/RHS of the map entry Value second = 2; } +// Contents of a `Value::map`: a collection of (key, value) pairs message MapValue { + // List of mappings. + // (Note: keys of a protobuf `map` cannot be message types like `Value`.) repeated PairValue pairs = 1; } +// Contents of a `Value::vec`: a list of values message VecValue { + // Elements of the list repeated Value vec = 2; } +// A `Value` tagged with a string to make a disjoint union of component types message VariantValue { + // Label, selects the appropriate handler of a `Node::match` + // destructuring the variant string tag = 1; + + // Contents Value value = 2; } +// A value that can be passed around a Tierkreis graph. message Value { + // Which kind of Value oneof value { + // A Tierkreis Graph (i.e., a higher-order function value) Graph graph = 1; + // Signed 64-bit integer value int64 integer = 2; + + // Boolean value bool boolean = 3; + // String value string str = 9; + // Double-precision (64-bit) floating-point value double flt = 10; // OptionValue option = 11; // REMOVED + // A pair of two other values PairValue pair = 4; + // An arbitrary-length list of values VecValue vec = 5; + // 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 MapValue map = 7; + + // A structure (aka record) value with string-named fields (each storing a `Value`) StructValue struct = 8; + + // A value tagged with a string to make a value of sum type (see `Type::Variant`) VariantValue variant = 12; } } +// A value was placed onto an edge - used for visualization by the Python runtime. message Output { + // The edge (of the outermost graph) onto which the value was placed Edge edge = 1; + // The value, i.e. an intermediate during graph execution Value value = 2; } +// A list of all the `Output`s, used for visualization by the Python runtime. message OutputStream { + // All the `Output`s so far. repeated Output stream = 1; } +/// 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). message Type { + // which variety (and thus also `Kind`) of Type oneof type { + // Type variable, used inside polymorphic `TypeScheme`s only. + // Can be of [Kind::Row] or [Kind::Star]. string var = 1; + // Type of signed integers (values must be `Value::int`) Empty int = 2; + // Type of booleans (values must be `Value::bool`) Empty bool = 3; + // Type of `Value::graph`s, with a specified row of inputs and outputs GraphType graph = 4; + // Type of `Value::pair`s, with types for the two elements PairType pair = 5; + // Type of arbitrary-length homogeneous lists, with the type for all elements. + // Values must be `Value::vec`. Type vec = 6; + // 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`. RowType row = 7; + // 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. PairType map = 9; + // Type of `Value::struct`s with a specified row of field names and types. + // Optionally, the struct type itself may have a name. StructType struct = 10; + // Type of strings (values must be `Value::str`) Empty str = 11; + // Type of double-precision floats () Empty flt = 12; + // Type option = 13; // REMOVED + + // 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). RowType variant = 14; } } +// Describes the type of a graph by specifying the input and output rows message GraphType { + // The inputs to the graph (known and/or variable) RowType inputs = 1; + // The outputs to the graph (known and/or variable) RowType outputs = 2; } +// Used to describe the type of a pair, or of a map (key + value) message PairType { + // Type of the first element of the pair, or the keys in the map Type first = 1; + // Type of the second element of the pair, or the values in the map Type second = 2; } +// An unordered set of names (strings), each with a type; possibly +// plus a variable (of `Kind::Row`) to stand for an unknown portion. message RowType { + // Known labels, and types for each (of course these may contain variables) map content = 1; + // 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` string rest = 2; } +// Describes a `Type::Struct`: the row of fields, optionally with a name for the type. message StructType { + // The fields in the struct RowType shape = 1; + // Wrapper to make name optional oneof name_opt { + // Optional name of the struct type. (TODO how is this used?) string name = 2; } } +// Qualified name of a function message FunctionName { + // Identifies a `tierkreis.v1alpha1.signature.Namespace` (perhaps a subspace) + // by traversing downwards from the root repeated string namespaces = 1; + // Name of the function within that subspace string name = 2; } +// Describes a `Node::function`, identifying the function to call, +// and optionally a time after which execution should be retried message FunctionNode { + // Qualified name of the function FunctionName name = 1; + // Time after which execution of the function may be assumed to have + // failed and should be retried (in due course) optional uint32 retry_secs = 2; } +// Describes a `Node::box`: a location at which to run a graph message BoxNode { + // On which runtime to run the subgraph Location loc = 1; + // Definition of the graph to run Graph graph = 2; } +// A node in a `Graph` message Node { + // Basic varieties of node, most leaf nodes are different `Node::function`s oneof node { + // The node that emits a graph's input values. Empty input = 1; + + // The node that receives a graph's output values. Empty output = 2; + + // A node that emits a constant value (contained within). Value const = 3; + + // 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. BoxNode box = 4; + + // 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. FunctionNode function = 5; + + // Perform pattern matching on a variant value, with handlers according to the + // input `Type::variant` Empty match = 6; + + // Create a variant. Tag(tag) :: forall T. T -> Variant(tag:T | ...) string tag = 7; } } +// An edge in a `Graph` message Edge { + // Source (out-)port of `node_from` string port_from = 1; + + // Target/destination (in-)port of `node_to` string port_to = 2; + + // Source node uint32 node_from = 3; + + // Source port uint32 node_to = 4; + + // Explicit annotation of the type of the edge. Client may (optionally) + // provide; typechecking will (always) fill in. Type edge_type = 5; } +// 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. message Graph { + // 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. repeated Node nodes = 1; + + // The edges in the graph. Each can optionally be annotated with a type. repeated Edge edges = 2; + + // User-provided name of the graph. Used for debug/display, does not affect execution. string name = 3; - // below orderings are optional and used by graph builders if available - // listed ports will be reported and wired up in the listed order + + // 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 repeated string input_order = 4; + + // Like `input_order` but for the output ports. repeated string output_order = 5; } @@ -151,38 +297,68 @@ message Graph { message Empty { } +// A polymorphic type scheme. Usually (but not necessarily) for a function. message TypeScheme { + // 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. repeated TypeSchemeVar variables = 1; + // Constraints restricting the legal instantiations of the [Self::variables] repeated Constraint constraints = 2; + // 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. Type body = 3; } +// Describes a variable bound by a `TypeScheme` message TypeSchemeVar { + // Name of the variable string name = 1; + // Whether the variable is a `Type` of `Value`s or a `RowType` Kind kind = 2; } +// Specifies a restriction on possible instantiations of type variables in a [TypeScheme] message Constraint { + // The two varieties of constraint oneof constraint { + // A type variable of `Kind::Row` does *not* have a specified label LacksConstraint lacks = 1; + // A row is the union of two other rows (we expect at least two of the three + // rows to contain variables) PartitionConstraint partition = 2; } } +// `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) message PartitionConstraint { + // One input to the union - a `Type::Row` or a `Type::Var` of `Kind::Row` Type left = 1; + // The other input to the union - a `Type::Row` or a `Type::Var` of `Kind::Row` Type right = 2; + // The result, i.e. `left` and `right` merged together. + // Could be a `Type::Row` or a `Type::Var` of `Kind::Row` Type union = 3; } +// `Constraint` that a row does not contain an element with the specified name message LacksConstraint { + // A `Type::row` or a `Type::var` of `Kind::row` Type row = 1; + // Field name that must not be present in `row` string label = 2; } +// The kind of a type variable - i.e. whether the "type" variable stands for +// a single type, or (some part of) a row. message Kind { + // The two possible kinds oneof kind { + // Kind of types (describing sets of values) Empty star = 1; + // Kind of rows (unordered names each with a type) Empty row = 2; } } diff --git a/python/protos/v1alpha1/runtime.proto b/python/protos/v1alpha1/runtime.proto index 5c06f32..32656a8 100644 --- a/python/protos/v1alpha1/runtime.proto +++ b/python/protos/v1alpha1/runtime.proto @@ -1,30 +1,55 @@ syntax = "proto3"; import "v1alpha1/graph.proto"; import "v1alpha1/signature.proto"; + +// Messages for running graphs and managing communication between a chain of Runtimes package tierkreis.v1alpha1.runtime; +// Runtime service supports running graphs. (Does not necessarily +// include `tierkreis.v1alpha1.signature.TypeInference` or +// `tierkreis.v1alpha1.signature.Signature` although implementations +// typically do all three.) service Runtime { + // Run a graph (blocking until success, or definite failure) rpc RunGraph (RunGraphRequest) returns (RunGraphResponse) {}; } +// A target to which to send `Runtime::RunGraph` +// or `tierkreis.v1alpha1.worker.Worker.RunFunction` callback requests message Callback { + // Connection point - host and address string uri = 1; + // location to pass as `RunGraphRequest.loc` or `tierkreis.v1alpha1.worker.RunFunctionRequest.loc` tierkreis.v1alpha1.graph.Location loc = 2; } - +// `Runtime.RunGraph` request to run a graph with inputs message RunGraphRequest { + // Graph to run tierkreis.v1alpha1.graph.Graph graph = 1; + // Inputs to pass to the graph tierkreis.v1alpha1.graph.StructValue inputs = 2; + // Hint as to whether the runtime should type-check the graph (against the inputs) + // before executing any nodes. (If absent, defaults to false.) bool type_check = 3; + // Location (a child of this Runtime, or a path down a forwarding chain to a parent Runtime) tierkreis.v1alpha1.graph.Location loc = 4; + // 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. Callback escape = 5; } +// Result of a `Runtime.RunGraph` request message RunGraphResponse { + // The three possible outcomes - success or failure from typecheck or runtime. oneof result { + // Graph ran to completion and produced the given outputs (a string-to-Value map) tierkreis.v1alpha1.graph.StructValue success = 1; + // A runtime error during execution of the Graph string error = 2; + // The Graph failed type-checking before execution started + // (only if `RunGraphRequest.type_check` was true.) tierkreis.v1alpha1.signature.TypeErrors type_errors = 3; } } diff --git a/python/protos/v1alpha1/signature.proto b/python/protos/v1alpha1/signature.proto index 8ce3739..363ba3e 100644 --- a/python/protos/v1alpha1/signature.proto +++ b/python/protos/v1alpha1/signature.proto @@ -1,62 +1,117 @@ syntax = "proto3"; -package tierkreis.v1alpha1.signature; import "v1alpha1/graph.proto"; +// Messages for querying a Runtime about what functions and graphs it can run +// (for graphs this means type-checking). +package tierkreis.v1alpha1.signature; + +// Information about a function: its polymorphic [TypeScheme], user-readable +// description, and port ordering (for debug/display purposes, not used by +// the typechecker). message FunctionDeclaration { + /// Polymorphic type scheme describing all possible input/output types tierkreis.v1alpha1.graph.TypeScheme type_scheme = 2; + // Human-readable documentation string description = 3; + // Order in which to display input ports repeated string input_order = 4; + // Order in which to display output ports repeated string output_order = 5; } +// Request to list the functions known to a Runtime, optionally filtering by a Location message ListFunctionsRequest { + // Filter to only report functions available in the specified location or children + // thereof. (The default/empty location means the root, i.e. all functions.) tierkreis.v1alpha1.graph.Location loc = 1; } +// aka the "Signature" of a Runtime: the `FunctionDeclaration`s and +// descendant `Location`s that it knows. message ListFunctionsResponse { + // Every function the runtime can run, and the locations in which it can run each Namespace root = 1; + // Named aliases for polymorphic types map aliases = 2; + // All the locations the Runtime knows. repeated tierkreis.v1alpha1.graph.Location scopes = 3; } +// Service for querying a Runtime for the functions and locations that it knows service Signature { + // List the `FunctionDeclaration`s and descendant `Location`s of a given `Location` rpc ListFunctions (ListFunctionsRequest) returns (ListFunctionsResponse) {}; } +// Service for inferring the type of a value (e.g. graph). (Usually +// implemented by a Runtime but can be separate.) service TypeInference { + // Infers the type of a value, e.g. a graph. (A graph with inputs can be + // encoded by placing the inputs into the graph as constants.) + // If successful, the value returned will be the same, except that if a graph, + // edges within it will have type annotations. rpc InferType (InferTypeRequest) returns (InferTypeResponse) {}; } +// Request to infer the type of a graph (used for `TypeInference::InferType`) message InferTypeRequest { + // Value whose type (scheme) to infer tierkreis.v1alpha1.graph.Value value = 1; + // 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. tierkreis.v1alpha1.graph.Location loc = 2; } +// Result of inferring the type of a value via `TypeInference::InferType` message InferTypeResponse { + // Success or failure of `TypeInference::InferType` oneof response { + // Inference succeeded, return the type-annotated value and inferred type (scheme) InferTypeSuccess success = 1; + // Type inference failed because of one or more type errors TypeErrors error = 2; } } +// A type successfully inferred by `TypeInference::InferType` message InferTypeSuccess { + // 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`). tierkreis.v1alpha1.graph.Value value = 1; + // 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.) tierkreis.v1alpha1.graph.TypeScheme type_scheme = 2; } +// A graph with (optionally) input values for it. Used for +// `InferGraphTypesRequest`s and `InferGraphTypesResponse`. message GraphWithInputs { + // The graph tierkreis.v1alpha1.graph.Graph graph = 1; + // Optionally, input values to feed to the graph optional tierkreis.v1alpha1.graph.StructValue inputs = 2; } +// Used by `tierkreis-typecheck` Rust(PYO3)/python interop library +// to request type inference of a graph with input values. message InferGraphTypesRequest { + // Graph and inputs whose types to infer GraphWithInputs gwi = 1; + // The signature of functions to check against. Namespace functions = 2; } +// Used by `tierkreis-typecheck` Rust(PYO3)/python interop library +// to report results of type inference on a graph with input values. message InferGraphTypesResponse { + // Success or failure of type inference PYO3 API oneof response { + // Inference was successful, return the type GraphWithInputs success = 1; + // Inference failed due to one or more `TierkreisTypeError`s TypeErrors error = 2; } } @@ -66,60 +121,109 @@ message InferGraphTypesResponse { message Empty { } +// A series of these identifies where in a `Value::graph` or `GraphWithInputs` +// was the cause of a `TierkreisTypeError`. message GraphLocation { oneof location { - Empty root = 1; + //Empty root = 1; // REMOVED, was redundant, always the first element + + // Where the previous location(s) identify a `Value::vec`, identifies one element by index uint32 vec_index = 2; + // 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.) uint32 node_idx = 4; + // Where the previous location(s) identify a graph, the error is on the indicated edge. + // (Will end the sequence of `GraphLocation`s.) tierkreis.v1alpha1.graph.Edge edge = 5; + // Where previous location(s) identify a graph, the error is in the input node Empty input = 6; + // Where previous location(s) identify a graph, the error is in the output node Empty output = 7; + // Where the previous location(s) identify a struct value (e.g. inside a Const), + // the error is in the named field of that struct string struct_field = 8; + // Where the previous location(s) identify a `Value::pair`, the error is in the first element Empty pair_first = 9; + // Where the previous location(s) identify a `Value::pair`, the error is in the second element Empty pair_second = 10; + // Where the previous location(s) identify a `Value::map`, the error is in one of the keys + // (does not specify which) Empty map_key = 11; + // Where the previous location(s) identify a `Value::map`, the error is in one of the values + // (does not specify which) Empty map_value = 12; + // For `TypeInference::InferType`, + // indicates the error is in one of the `GraphWithInputs::inputs` string input_value = 13; } } +// A Collection of `TypeError`s message TypeErrors { + // List of errors repeated TierkreisTypeError errors = 1; } +// `ErrorVariant` that two types failed to unify. message UnifyError { + // The type that was expected. tierkreis.v1alpha1.graph.Type expected = 1; + // The type that was actually inferred. tierkreis.v1alpha1.graph.Type found = 2; } +// `ErrorVariant` that a type scheme is ill-formed because it refers to an unknown +// type variable. message TypeVarError { + // The unknown type variable. tierkreis.v1alpha1.graph.TypeSchemeVar variable = 1; + // The ill-formed type scheme. tierkreis.v1alpha1.graph.TypeScheme type_scheme = 2; } +// Errors that can occur during type checking. message ErrorVariant { + // Errors that can occur during type checking. oneof error { + // Two types failed to unify. UnifyError unify = 1; + // A type scheme is ill-formed due to a kind mismatch. string kind = 2; + // A graph referred to an unknown function. tierkreis.v1alpha1.graph.FunctionName unknown_function = 3; + // A type scheme is ill-formed because it refers to an unknown type variable. TypeVarError unknown_type_var = 4; + // A type constraint (`LacksConstraint` or `PartitionConstraint`) is unsatisfiable. string bound = 5; } } +// An error preventing type inference in a graph passed to `TypeInference::InferType` +// or `tierkreis.v1alpha1.runtime.Runime.RunGraph` message TierkreisTypeError { + // Detail of the error ErrorVariant variant = 1; - // locations go from outermost to innermost - // in nested const/box graphs + // Identifies where in the value/graph the error occurred. + // Locations go from outermost to innermost in nested const/box graphs. repeated GraphLocation location = 2; } +// A `FunctionDeclaration` with a set of `Location`s at which +// the function is supported. message NamespaceItem { + // Declaration of the function, including typescheme; + // identical at all `locations` FunctionDeclaration decl = 1; + // The locations (aka scopes) at which the function is supported repeated tierkreis.v1alpha1.graph.Location locations = 2; } +// Tree-structured mapping (sharing common prefixes) from +// `tierkreis.v1alpha1.graph.FunctionName`s to `NamespaceItem`s message Namespace { + // `NamespaceItem`s at this level of the qualified-name hierarchy map functions = 1; + // Mappings for subtrees of the name hierarchy, i.e. for qualnames + // with a longer prefix (the map key being the next atom of prefix) map subspaces = 2; } diff --git a/python/protos/v1alpha1/worker.proto b/python/protos/v1alpha1/worker.proto index 647f689..5a58d88 100644 --- a/python/protos/v1alpha1/worker.proto +++ b/python/protos/v1alpha1/worker.proto @@ -1,19 +1,35 @@ syntax = "proto3"; import "v1alpha1/graph.proto"; import "v1alpha1/runtime.proto"; + +// Messages for running functions on a worker package tierkreis.v1alpha1.worker; +// Request for `Worker::RunFunction` to run a named function message RunFunctionRequest { + // Name of the function to run tierkreis.v1alpha1.graph.FunctionName function = 1; + // Inputs to pass to the function tierkreis.v1alpha1.graph.StructValue inputs = 2; + // 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. tierkreis.v1alpha1.graph.Location loc = 3; + // 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. tierkreis.v1alpha1.runtime.Callback callback = 4; } +// Result of `Worker::RunFunction` (success case - errors are reported as GRPC errors) message RunFunctionResponse { + // Result values named by port tierkreis.v1alpha1.graph.StructValue outputs = 1; } +// A worker is anything that can run functions (typically including any Runtime, +// which can run functions itself and also on behalf of any child workers) service Worker { + // Runs a named function, blocking until completion rpc RunFunction (RunFunctionRequest) returns (RunFunctionResponse) {} } diff --git a/python/pyproject.toml b/python/pyproject.toml index e20da87..5f379fe 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -14,9 +14,9 @@ requires-python = ">=3.10,<3.13" dependencies = [ "betterproto[compiler]==2.0.0b6", "grpclib>=0.4.3rc,<0.5", # pre-release to support python 3.10 - "networkx>=2.6.3,<3", + "networkx>=2.6.3,<4", "graphviz>=0.20,<0.21", - "click==8.1.3", # 8.1.4 causes mypy fails + "click>=8.1.3,<9", "yachalk>=0.1.4,<0.2", "requests>=2.31,<3", "pydantic~=2.5", @@ -26,31 +26,18 @@ dependencies = [ tkrs = 'tierkreis.cli:cli' [project.optional-dependencies] -docker = ["docker>=5,<6"] - +docker = ["docker>=6,<7"] telemetry = [ "opentelemetry-sdk>=1.15.0,<2", "opentelemetry-exporter-otlp>=1.15.0,<2", ] - commontypes = ["pytket>=1.0"] -lint = [ - "ruff~=0.3", - "pyright==1.1.345", - "tierkreis[sc22-example,telemetry,docker]", -] -test = [ - "pytest>=6.2,<7", - "pytest-asyncio>=0.16,<0.17", - "pytest-cov>=5.0,<6", - "tierkreis[commontypes,typecheck]", -] sc22-example = ["numpy>=1.20,<2", "pytket>=1.0"] +typecheck = ["tierkreis-typecheck"] -typecheck = ["tierkreis_typecheck>=0.2,<0.3"] - - -docs = ["sphinx>=4.3", "sphinx-book-theme>=1.1.2"] +[dependency-groups] +dev = ["tierkreis[telemetry,typecheck,docker,sc22-example,commontypes]"] +build = ["build[uv]"] [tool.pytest.ini_options] markers = ["pytket"] @@ -72,7 +59,7 @@ include = ["tierkreis*", "tierkreis/py.typed"] target-version = "py310" # default + imports lint.select = ["E4", "E7", "E9", "F", "I"] -extend-exclude = ["tierkreis/core/protos"] +extend-exclude = ["tierkreis/core/protos", "examples"] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] # module imported but unused @@ -81,3 +68,6 @@ extend-exclude = ["tierkreis/core/protos"] include = ["."] exclude = ["tierkreis/core/protos", "build"] ignore = ["^build/", "pytket_worker"] + +[tool.uv.sources] +tierkreis-typecheck = { workspace = true } diff --git a/python/pytket_worker/pytket_worker/main.py b/python/pytket_worker/pytket_worker/main.py old mode 100644 new mode 100755 index 9e7c5ee..1c41c62 --- a/python/pytket_worker/pytket_worker/main.py +++ b/python/pytket_worker/pytket_worker/main.py @@ -1,5 +1,6 @@ #!/bin/sh "exec" "$(dirname $0)/../.venv/bin/python" "$0" "$@" + from __future__ import annotations import json diff --git a/python/tests/test_builder.py b/python/tests/test_builder.py index 9c3be43..e5f38f7 100644 --- a/python/tests/test_builder.py +++ b/python/tests/test_builder.py @@ -90,9 +90,9 @@ def _structs_graph() -> TierkreisGraph: "make_struct", **map_vals(dict(height=12.3, name="hello", age=23), tg.add_const), ) - sturct = tg.add_func("unpack_struct", struct=factory["struct"]) + struct = tg.add_func("unpack_struct", struct=factory["struct"]) - tg.set_outputs(value=sturct["age"]) + tg.set_outputs(value=struct["age"]) tg.output_order = [Labels.VALUE] return tg @@ -197,7 +197,7 @@ class Point: p2: IntValue @graph() - def struc_id(in_st: ValueSource) -> Output: + def struct_id(in_st: ValueSource) -> Output: return Output(in_st) @graph() @@ -228,7 +228,7 @@ def loop_def(initial_): Break(initial) return Output(loop_ifelse.nref) - _disc = struc_id(Const(Point(FloatValue(4.3e1), IntValue(3)))) + _disc = struct_id(Const(Point(FloatValue(4.3e1), IntValue(3)))) return Output(o1=ifelse.nref, o2=loop_def(other_total)) @@ -512,23 +512,23 @@ def num_unpacks() -> int: def foo(a: ValueSource, b: ValueSource) -> Output: f = bi.make_struct(foo=a, bar=b) id_: "NodeRef" = pn.id_py(value=f["struct"]) - sturct: "NodePort" = id_["value"] + struct: "NodePort" = id_["value"] old_proto = current_graph().to_proto() - foo: "StablePortFunc" = sturct["foo"] + foo: "StablePortFunc" = struct["foo"] assert num_unpacks() == 0 assert current_graph().to_proto() == old_proto # Nothing changed yet first = bi.id(foo) assert num_unpacks() == 1 - second = bi.iadd(a=sturct["bar"], b=Const(1)) + second = bi.iadd(a=struct["bar"], b=Const(1)) assert num_unpacks() == 1 # Repeated uses of the same struct member need an explicit copy proto = current_graph().to_proto() with pytest.raises(ValueError, match="Already an edge from"): current_graph().add_edge( - sturct["foo"].resolve(), current_graph().output["second"] + struct["foo"].resolve(), current_graph().output["second"] ) assert proto == current_graph().to_proto() # Did nothing return Output(first=first, second=second) @@ -547,19 +547,19 @@ def num_copies_unpacks() -> Tuple[int, int]: def test_cant_unpack_original_after_copy(bi: Namespace) -> None: @graph() def foo(a: ValueSource, b: ValueSource) -> Output: - sturct: ValueSource = bi.make_struct(foo=a, bar=b)["struct"] + struct: ValueSource = bi.make_struct(foo=a, bar=b)["struct"] - bi.discard(sturct["foo"]) + bi.discard(struct["foo"]) assert num_copies_unpacks() == (0, 1) - cp = Copyable(sturct) + cp = Copyable(struct) o: Output = Output(whole=cp) assert num_copies_unpacks() == (1, 1) proto = current_graph().to_proto() with pytest.raises(ValueError, match="Cannot unpack"): current_graph().add_edge( - sturct["bar"].resolve(), current_graph().output["second"] + struct["bar"].resolve(), current_graph().output["second"] ) assert proto == current_graph().to_proto() # Did nothing return o @@ -571,12 +571,12 @@ async def test_can_unpack_copy_with_resolve( ) -> None: @graph() def foo(a: ValueSource, b: ValueSource) -> Output: - sturct = bi.make_struct(foo=a, bar=b)["struct"] + struct = bi.make_struct(foo=a, bar=b)["struct"] - s = bi.iadd(a=sturct["foo"], b=sturct["bar"]) + s = bi.iadd(a=struct["foo"], b=struct["bar"]) assert num_copies_unpacks() == (0, 1) - cp = Copyable(sturct).resolve() + cp = Copyable(struct).resolve() o: Output = Output(sum=s, product=bi.imul(a=cp["foo"], b=cp["bar"])) assert num_copies_unpacks() == (1, 2) @@ -590,14 +590,14 @@ def foo(a: ValueSource, b: ValueSource) -> Output: async def test_Copyable_fields(bi: Namespace, client: RuntimeClient) -> None: @graph() def foo(a: ValueSource, b: ValueSource) -> Output: - sturct = bi.make_struct(foo=a, bar=b)["struct"] + struct = bi.make_struct(foo=a, bar=b)["struct"] - s = bi.iadd(a=sturct["foo"], b=sturct["bar"]) + s = bi.iadd(a=struct["foo"], b=struct["bar"]) assert num_copies_unpacks() == (0, 1) return Output( sum=s, - product=bi.imul(a=Copyable(sturct["foo"]), b=Copyable(sturct["bar"])), + product=bi.imul(a=Copyable(struct["foo"]), b=Copyable(struct["bar"])), ) outputs = await client.run_graph(foo, a=3, b=4) diff --git a/python/tests/test_frontend.py b/python/tests/test_frontend.py index cc7e218..f0a6647 100644 --- a/python/tests/test_frontend.py +++ b/python/tests/test_frontend.py @@ -188,7 +188,7 @@ async def test_vec_sequence(client: RuntimeClient) -> None: outputs["sequenced"].to_python(TierkreisGraph).inline_boxes() ) - # check composition is succesful + # check composition is successful functions = { node.function_name.name for node in sequenced_g.nodes() @@ -221,6 +221,26 @@ def test_merge_copies(): assert sum(1 for _ in tg.edges()) == 6 +# https://github.com/CQCL-DEV/tierkreis/issues/526 +def test_merge_copies_bug(): + tg = TierkreisGraph() + x1, x2 = tg.copy_value(tg.input["x"]) + x3, x4 = tg.copy_value(x1) + tg.discard(x2) + tg.discard(x4) + tg.set_outputs(value=x3) + + assert tg.n_nodes == 6 + assert len(list(tg.nodes())) == 6 + assert sum(1 for _ in tg.edges()) == 5 + + tg = _merge_copies(tg) + + assert tg.n_nodes == 5 + assert len(list(tg.nodes())) == 5 + assert sum(1 for _ in tg.edges()) == 4 + + @pytest.mark.asyncio async def test_subspaces(client: RuntimeClient): tg = TierkreisGraph() diff --git a/python/tests/test_generics.py b/python/tests/test_generics.py index 0863f3d..8f16d43 100644 --- a/python/tests/test_generics.py +++ b/python/tests/test_generics.py @@ -45,15 +45,15 @@ async def test_generic(bi, client: RuntimeClient) -> None: with pytest.raises(IncompatibleAnnotatedValue, match="~G"): # value doesn't know it is instance of concrete type so fails. TierkreisValue.from_python(py_val) - # whereas the pydantic-validated instantation records its own concrete type + # whereas the pydantic-validated instantiation records its own concrete type assert TierkreisValue.from_python(py_val_explicit_type) == tk_val assert tk_val.to_python(py_type) == py_val - pn = bi["python_nodes"] + on = bi["python_nodes"] @graph() def g() -> Output: - return Output(pn.generic_int_to_str(Const(py_val, py_type))) + return Output(on.generic_int_to_str(Const(py_val, py_type))) out = (await client.run_graph(g))["value"] @@ -77,15 +77,15 @@ async def test_generic_list(bi, client: RuntimeClient) -> None: with pytest.raises(IncompatibleAnnotatedValue, match="~G"): # value doesn't know it is instance of concrete type so fails. TierkreisValue.from_python(py_val) - # whereas the pydantic-validated instantation records its own concrete type + # whereas the pydantic-validated instantiation records its own concrete type assert TierkreisValue.from_python(py_val_explicit_type) == tk_val assert tk_val.to_python(py_type) == py_val - pn = bi["python_nodes"] + on = bi["python_nodes"] @graph() def g() -> Output: - return Output(pn.generic_int_to_str_list(Const(py_val, py_type))) + return Output(on.generic_int_to_str_list(Const(py_val, py_type))) out = (await client.run_graph(g))["value"] @@ -160,7 +160,7 @@ def make_inner() -> Inner[int]: def test_generic_origin() -> None: - assert generic_origin(list[int]) == list + assert generic_origin(list[int]) is list assert generic_origin(list) is None @dataclass diff --git a/python/tests/test_type_check.py b/python/tests/test_type_check.py index f2c1720..8c5e673 100644 --- a/python/tests/test_type_check.py +++ b/python/tests/test_type_check.py @@ -88,7 +88,7 @@ async def test_infer_errors(client: RuntimeClient) -> None: locs = (e.proto_err.location for e in errs) locations = { - betterproto.which_one_of(loc[1], "location")[0]: loc[1] for loc in locs + betterproto.which_one_of(loc[0], "location")[0]: loc[0] for loc in locs } assert set(locations.keys()) == {"input", "node_idx"} assert locations["node_idx"].node_idx == node_1.idx @@ -188,10 +188,10 @@ async def test_infer_graph_types_with_inputs( assert len(err.value) == 1 e = err.value.errors[0] assert isinstance(e, UnifyError) - # TODO location sometimes - assert betterproto.which_one_of(e.proto_err.location[-1], "location")[0] in ( - "input", - "root", + # TODO location sometimes absent + assert ( + len(e.proto_err.location) == 0 + or betterproto.which_one_of(e.proto_err.location[-1], "location")[0] == "input" ) # deep copy (above) has no annotations yet, so ok tg2, inputs_ = infer_graph_types(tg2, funcs, graph_inputs) @@ -240,7 +240,7 @@ def _extract_dbg_info(errstrings: str) -> Iterator[tuple[str, int]]: @pytest.mark.asyncio async def test_builder_debug(bi: "BuilderNS", sig: Signature): # test debug string is correctly found - # checks that "ERROR" preceeds the location of reported errors + # checks that "ERROR" precedes the location of reported errors def _bad_nod(): # ERROR return bi.iadd(Const(1), Const(1.0)) diff --git a/python/tests/test_variants.py b/python/tests/test_variants.py index ccfd5a9..6dcb182 100644 --- a/python/tests/test_variants.py +++ b/python/tests/test_variants.py @@ -2,6 +2,7 @@ import warnings from dataclasses import dataclass, field from enum import Enum +from types import UnionType from typing import Any, Literal, Type, Union import pydantic as pyd @@ -31,9 +32,9 @@ async def test_union_types(bi, client: RuntimeClient) -> None: # through worker functions that process them as unions, and converted back # to unions. iv = TierkreisValue.from_python(1) - assert iv.to_python(float | int) == 1 + assert iv.to_python_union(float | int) == 1 fv = TierkreisValue.from_python(2.3) - assert fv.to_python(int | float) == 2.3 + assert fv.to_python_union(int | float) == 2.3 @dataclass class StructWithUnion: @@ -87,8 +88,8 @@ def g2() -> Output: out = await client.run_graph(g2) a, b = out["a"], out["b"] - assert a.to_python(int | float) == 1 - assert b.to_python(int | float) == 1.0 + assert a.to_python_union(int | float) == 1 + assert b.to_python_union(int | float) == 1.0 @pyd.dataclasses.dataclass(frozen=True, kw_only=True) @@ -148,6 +149,24 @@ class OpaqueContainsVariant(OpaqueModel): variant: ComplexVariant | SimpleVariant = pyd.Field(discriminator="disc_name") +def _tktype_from_py(py_type: UnionType | Type) -> TierkreisType: + if isinstance(py_type, UnionType): + return TierkreisType.from_python_union(py_type) + return TierkreisType.from_python(py_type) + + +def _tkval_from_py(py_val: Any, py_type: UnionType | Type | None) -> TierkreisValue: + if isinstance(py_type, UnionType): + return TierkreisValue.from_python_union(py_val, py_type) + return TierkreisValue.from_python(py_val, py_type) + + +def _tkval_to_py(tkv: TierkreisValue, py_type: UnionType | Type) -> Any: + if isinstance(py_type, UnionType): + return tkv.to_python_union(py_type) + return tkv.to_python(py_type) + + @pytest.mark.asyncio async def test_pydantic_types(bi, client: RuntimeClient) -> None: constrained = ConstrainedField(foo=1, points=(1.2,)) @@ -237,7 +256,7 @@ async def test_pydantic_types(bi, client: RuntimeClient) -> None: def mk_mygeneric_variant(typ: Type, val: TierkreisValue) -> StructValue: return StructValue({"x": VariantValue(UnionTag.type_tag(typ), val)}) - samples: list[tuple[Type, Any, TierkreisValue]] = [ + samples: list[tuple[Type | UnionType, Any, TierkreisValue]] = [ (ConstrainedField, constrained, tk_constrained), (SimpleVariant, simple, tk_simple), (EnumExample, EnumExample.First, tk_first), @@ -261,11 +280,11 @@ def mk_mygeneric_variant(typ: Type, val: TierkreisValue) -> StructValue: ] for py_type, py_val, expected_tk_val in samples: - _ = TierkreisType.from_python(py_type) + _ = _tktype_from_py(py_type) converted_tk_val = TierkreisValue.from_python(py_val) assert converted_tk_val == expected_tk_val - assert TierkreisValue.from_python(py_val, py_type) == expected_tk_val - assert converted_tk_val.to_python(py_type) == py_val + assert _tkval_from_py(py_val, py_type) == expected_tk_val + assert _tkval_to_py(converted_tk_val, py_type) == py_val pn = bi["python_nodes"] @@ -275,7 +294,7 @@ def g() -> Output: out = (await client.run_graph(g))["value"] - assert out.to_python(py_type) == py_val + assert _tkval_to_py(out, py_type) == py_val def test_union_over_arguments() -> None: @@ -288,11 +307,11 @@ def test_union_over_arguments() -> None: for val in values: for typ in types: - tk_val = TierkreisValue.from_python(val, typ) - assert tk_val.to_python(typ) == val + tk_val = TierkreisValue.from_python_union(val, typ) + assert tk_val.to_python_union(typ) == val # Separately test value+typ embedded together in an outer MyGeneric - typ2 = MyGeneric[typ] + typ2 = MyGeneric[typ] # type: ignore val2 = typ2(x=val) tk_val2 = TierkreisValue.from_python(val2, typ2) assert tk_val2.to_python(typ2) == val2 @@ -306,9 +325,9 @@ def test_reversed_union_enum() -> None: py_type, py_val, expected_tk_val = (None | EnumExample, EnumExample.First, tk_first) assert TierkreisValue.from_python(py_val) == expected_tk_val # Works the right way around: - assert expected_tk_val.to_python(EnumExample | None) == py_val + assert expected_tk_val.to_python_union(EnumExample | None) == py_val # But reverse the union and it does not (result is "None") - assert expected_tk_val.to_python(py_type) == py_val + assert expected_tk_val.to_python_union(py_type) == py_val def test_pydantic_generic_union() -> None: @@ -342,9 +361,9 @@ def g() -> Output: assert b.tag == UnionTag.type_tag(EmptyStruct) # to_python is tag-driven so ordering of union elements makes no difference - assert a.to_python(IntStruct | EmptyStruct) == IntStruct(1) - assert a.to_python(EmptyStruct | IntStruct) == IntStruct(1) - assert b.to_python(IntStruct | EmptyStruct) == EmptyStruct() + assert a.to_python_union(IntStruct | EmptyStruct) == IntStruct(1) + assert a.to_python_union(EmptyStruct | IntStruct) == IntStruct(1) + assert b.to_python_union(IntStruct | EmptyStruct) == EmptyStruct() # Make sure that the ordering of Union elements doesn't affect anything here @@ -382,5 +401,7 @@ def main() -> Output: return Output(res) res = await client.run_graph(main) - assert res.pop("value").to_python(MyGeneric[str] | int) == MyGeneric[str](x=msg) + assert res.pop("value").to_python_union(MyGeneric[str] | int) == MyGeneric[str]( + x=msg + ) assert res == {} # No other outputs besides x diff --git a/python/tierkreis/builder.py b/python/tierkreis/builder.py index 7e5e23b..14fcb79 100644 --- a/python/tierkreis/builder.py +++ b/python/tierkreis/builder.py @@ -800,15 +800,15 @@ def _combine_captures(thunks: list[CaptureBuilder]) -> dict[ValueSource, PortID] combined_inputs: dict[ValueSource, str] = { vs: next(names) for bg in thunks for vs in bg.captured } # Duplicate ValueSources overwrite earlier entries, making even sparser. - for vs, nam in combined_inputs.items(): + for vs, name in combined_inputs.items(): for bg in thunks: if in_name := bg.captured.get(vs): in_edge = bg.graph.out_edge_from_port(bg.graph.input[in_name]) assert in_edge is not None bg.graph._graph.remove_edge(*in_edge.to_edge_handle()) - bg.graph.add_edge(bg.graph.input[nam], in_edge.target) + bg.graph.add_edge(bg.graph.input[name], in_edge.target) else: - bg.graph.discard(bg.graph.input[nam]) + bg.graph.discard(bg.graph.input[name]) return combined_inputs @@ -993,10 +993,10 @@ def RenameOutputs(thunk: ValueSource, rename_map: dict[str, str]) -> NodePort: def _build_box( - g: TierkreisGraph, __tk_loc: Location, *args: ValueSource, **kwargs: ValueSource + g: TierkreisGraph, _tk_loc: Location, *args: ValueSource, **kwargs: ValueSource ) -> NodeRef: bx = _CallAddNode( - BoxNode(g, __tk_loc), + BoxNode(g, _tk_loc), g.input_order, g.output_order, ) diff --git a/python/tierkreis/cli.py b/python/tierkreis/cli.py index dc6ba36..26ae415 100644 --- a/python/tierkreis/cli.py +++ b/python/tierkreis/cli.py @@ -123,7 +123,7 @@ async def cli(ctx: click.Context, runtime: str, port: Optional[int]): ctx.obj["client_manager"] = client_manager -@cli.command() +@cli.command() # type: ignore @click.argument("proto", type=click.Path(exists=True)) @click.pass_context @coro @@ -135,7 +135,7 @@ async def check(ctx: click.Context, proto: str) -> TierkreisGraph: return tkg -@cli.command() +@cli.command() # type: ignore @click.argument("proto", type=click.Path(exists=True)) @click.argument( "view_path", @@ -186,7 +186,7 @@ def _print_typeerrs(errs: str): print(chalk.red(errs), file=sys.stderr) -@cli.command() +@cli.command() # type: ignore @click.argument("proto", type=click.Path(exists=True)) @click.argument("inputs", default="") @click.option("-p", "--py-inputs", default="") @@ -248,7 +248,7 @@ def _print_func(name: str, func: "FunctionDeclaration"): print(chalk.green(func.description)) -@cli.command() +@cli.command() # type: ignore @click.pass_context @click.option("--namespace", type=str, help="Show only signatures of this namespace.") @click.option( diff --git a/python/tierkreis/core/_internal.py b/python/tierkreis/core/_internal.py index 04beb8d..e90387c 100644 --- a/python/tierkreis/core/_internal.py +++ b/python/tierkreis/core/_internal.py @@ -1,4 +1,4 @@ -# Utitlities to extract fields from a class +# Utilities to extract fields from a class import inspect from dataclasses import dataclass, fields, is_dataclass diff --git a/python/tierkreis/core/graphviz.py b/python/tierkreis/core/graphviz.py index 41cadc9..0fd2bb1 100644 --- a/python/tierkreis/core/graphviz.py +++ b/python/tierkreis/core/graphviz.py @@ -1,9 +1,11 @@ """Visualise TierkreisGraph using graphviz.""" +import copy import textwrap from typing import Iterable, NewType, Optional, Tuple, cast import graphviz as gv +import networkx as nx from tierkreis.core.tierkreis_graph import ( BoxNode, @@ -19,7 +21,7 @@ TierkreisNode, ) -# old palettte: https://colorhunt.co/palette/343a407952b3ffc107e1e8eb +# old palette: https://colorhunt.co/palette/343a407952b3ffc107e1e8eb # _COLOURS = { # "background": "white", # "node": "#7952B3", @@ -439,6 +441,11 @@ def _merge_copies(g: TierkreisGraph) -> _CopyMergedGraph: """Merge adjacent copy nodes - adds extra ports to copy nodes so won't pass type check.""" candidates = {idx for idx, node in enumerate(g.nodes()) if node.is_copy_node()} + if len(candidates) == 0: + # no need to deepcopy graph + return _CopyMergedGraph(g) + + g = copy.deepcopy(g) while candidates: node_name = candidates.pop() copy_children = ( @@ -467,4 +474,6 @@ def _merge_copies(g: TierkreisGraph) -> _CopyMergedGraph: node_name, e.target.node_ref.idx, ("", e.target.port), type=e.type_ ) + # make node indices contiguous again + g._graph = nx.convert_node_labels_to_integers(g._graph) return _CopyMergedGraph(g) diff --git a/python/tierkreis/core/type_errors.py b/python/tierkreis/core/type_errors.py index 015dde5..3bd121b 100644 --- a/python/tierkreis/core/type_errors.py +++ b/python/tierkreis/core/type_errors.py @@ -123,9 +123,9 @@ def location_str(self) -> str: elif name == "input_value": locs.append(f"InputValue({out_type})") elif name == "edge": - tke = cast(pg.Edge, out_type) - src = _node_port_str(tke.node_from, tke.port_from, current_g) - tgt = _node_port_str(tke.node_to, tke.port_to, current_g) + edge = cast(pg.Edge, out_type) + src = _node_port_str(edge.node_from, edge.port_from, current_g) + tgt = _node_port_str(edge.node_to, edge.port_to, current_g) locs.append(f"Edge({src} -> {tgt})") elif name == "node_idx": idx = cast(int, out_type) diff --git a/python/tierkreis/core/type_inference.py b/python/tierkreis/core/type_inference.py index 020f105..6288205 100644 --- a/python/tierkreis/core/type_inference.py +++ b/python/tierkreis/core/type_inference.py @@ -54,7 +54,7 @@ def infer_graph_types( ) -> Union[TierkreisGraph, Tuple[TierkreisGraph, StructValue]]: """Infer the types in a graph and its inputs given a signature to check against. - If succesful both the graph and inputs are returned, with type annotations + If successful both the graph and inputs are returned, with type annotations added. If inputs are not provided, only the graph is returned. Raises `TierkreisTypeErrors` if the inference fails. diff --git a/python/tierkreis/core/types.py b/python/tierkreis/core/types.py index 7ae9a5e..1d3accc 100644 --- a/python/tierkreis/core/types.py +++ b/python/tierkreis/core/types.py @@ -230,6 +230,15 @@ def from_python( return result + @classmethod + def from_python_union( + cls, + type_: UnionType, + visited_types: typing.Optional[dict[typing.Type, "TierkreisType"]] = None, + ) -> "TierkreisType": + """Create a TierkreisType from a Union type.""" + return cls.from_python(type_, visited_types) # type: ignore + @classmethod def from_proto(cls, type_: pg.Type) -> "TierkreisType": """Attempts to convert a protobuf type to a TierkreisType subclass.""" diff --git a/python/tierkreis/core/values.py b/python/tierkreis/core/values.py index ece8a38..2d86d22 100644 --- a/python/tierkreis/core/values.py +++ b/python/tierkreis/core/values.py @@ -9,6 +9,7 @@ from abc import ABC, abstractmethod from dataclasses import Field, dataclass, fields, make_dataclass from enum import Enum +from types import UnionType from typing import ( Any, Callable, @@ -162,11 +163,15 @@ def to_python(self, type_: typing.Type[T]) -> T: continue raise ToPythonFailure(self) + def to_python_union(self, type_: UnionType) -> Any: + """Converts a tierkreis value to a python value given the desired union type.""" + return self.to_python(type_) # type: ignore + @classmethod def from_python( cls, value: Any, type_: typing.Type | None = None ) -> "TierkreisValue": - "Converts a python value to a tierkreis value. If `type_` is not provided, it is inferred from the value." + """Converts a python value to a tierkreis value. If `type_` is not provided, it is inferred from the value.""" try: some_type = type_ or type(value) # TODO find workaround for delayed imports @@ -194,13 +199,19 @@ def from_python( raise IncompatibleAnnotatedValue(value, type_) from e raise e + @classmethod + def from_python_union(cls, value: Any, type_: UnionType) -> "TierkreisValue": + """Converts a python value to a tierkreis value, given a target python Union type.""" + + return cls.from_python(value, type_) # type: ignore + def try_autopython(self) -> Optional[Any]: """Try to automatically convert to a python type without specifying the target types. - Returns None if unsuccesful. Expected to be succesful for "simple" or "leaf" + Returns None if unsuccessful. Expected to be successful for "simple" or "leaf" types, that are not made up component types. - :return: Python value if succesful, None if not. + :return: Python value if successful, None if not. :rtype: Optional[Any] """ try: diff --git a/python/tierkreis/pyruntime/python_builtin.py b/python/tierkreis/pyruntime/python_builtin.py index 316c6c2..58696a5 100644 --- a/python/tierkreis/pyruntime/python_builtin.py +++ b/python/tierkreis/pyruntime/python_builtin.py @@ -47,7 +47,7 @@ class CopyOut(Generic[a], UnpackRow): @namespace.function(type_vars={"a": StarKind()}) async def copy(value: a) -> CopyOut[a]: - "Copies its input value." + "Copies its input value to each of its two outputs." return CopyOut(value, deepcopy(value)) @@ -58,7 +58,7 @@ class EmptyOut(Generic[a], UnpackRow): @namespace.function(type_vars={"a": StarKind()}) async def discard(value: a) -> EmptyOut[a]: - """Deletes its input value.""" + """Ignores its input value, has no outputs.""" # things won't actually be deleted until python decides to return EmptyOut() @@ -70,7 +70,8 @@ class EqOut(UnpackRow): @namespace.function(type_vars={"val": StarKind()}) async def eq(value_0: val, value_1: val) -> EqOut: - """Check two values for equality.""" + """Check two input values of the same type for equality, \ +producing a boolean.""" return EqOut(value_0 == value_1) @@ -104,7 +105,8 @@ async def _eval(_client, _stack, _x: StructValue) -> StructValue: ) ), ), - description="Evaluates a graph.", + description="Evaluates the graph on the `thunk` input with other inputs " + "matching the graph inputs, producing outputs matching those of the graph.", input_order=["thunk"], ), ) @@ -184,7 +186,7 @@ async def iadd(a: int, b: int) -> int: @namespace.function("id", type_vars={"a": StarKind()}) async def _id(value: a) -> a: - """Passes on an arbitrary value unchanged.""" + """Passes a single, arbitrary, value from input to output.""" return value @@ -244,8 +246,8 @@ class _InsertOut(Generic[a, b], UnpackRow): @namespace.function(type_vars={"a": StarKind(), "b": StarKind()}) async def insert_key(map: dict[a, b], key: a, val: b) -> _InsertOut[a, b]: - """Insert a key value pair in to a map.\ - Existing keys will have their values replaced.""" + """Transforms an input `Map` into an output by adding an \ +entry (or replacing an existing one) for an input key and value.""" map[key] = val return _InsertOut(map) @@ -313,8 +315,9 @@ async def _loop(_client, _stack, _x: StructValue) -> StructValue: ) ), ), - description="Run a looping thunk while it returns" - " `continue` i.e. until it returns `break`.", + description="Repeatedly applies a `Graph` input while it produces \ +a `Variant` tagged `continue`, i.e. until it returns a value tagged `break`, \ +and then returns that value.", input_order=["body", "value"], output_order=["value"], ), @@ -328,7 +331,7 @@ class _MakePairOut(Generic[a, b], UnpackRow): @namespace.function(type_vars={"a": StarKind(), "b": StarKind()}) async def make_pair(first: a, second: b) -> _MakePairOut[a, b]: - "Creates a new pair." + """Makes an output `Pair` from two separate inputs.""" return _MakePairOut(TierkreisPair(first, second)) @@ -355,7 +358,8 @@ async def make_struct(_client, _stack, ins: StructValue) -> StructValue: ) ), ), - description="Construct a struct from incoming ports.", + description="Takes any number of inputs and produces a single `Struct` " + "output with a field for each input, field names matching input ports.", output_order=["struct"], ), ) @@ -446,7 +450,9 @@ class _PopOut(Generic[a], UnpackRow): @namespace.function(type_vars={"a": StarKind()}) async def pop(vec: list[a]) -> _PopOut[a]: - """Split the last item from a Vec.""" + """Pops the first element from an input `Vec`, returning said \ +element separately from the remainder `Vec`. Fails at runtime if the input `Vec` \ +is empty.""" item = vec.pop() return _PopOut(vec, item) @@ -458,7 +464,7 @@ class _PushOut(Generic[a], UnpackRow): @namespace.function(type_vars={"a": StarKind()}) async def push(vec: list[a], item: a) -> _PushOut[a]: - """Push an item on to end of a Vec.""" + """Adds an input element onto an input `Vec` to give an output `Vec`.""" vec.append(item) return _PushOut(vec) @@ -471,7 +477,7 @@ class _RemoveOut(Generic[a, b], UnpackRow): @namespace.function(type_vars={"a": StarKind(), "b": StarKind()}) async def remove_key(map: dict[a, b], key: a) -> _RemoveOut[a, b]: - """Remove a key value pair from a map and return the map and value.""" + """Remove a key (input) from a map (input), return the map and value.""" val = map.pop(key) return _RemoveOut(map, val) @@ -611,7 +617,8 @@ async def _parallel(_client, _stack, inputs: StructValue) -> StructValue: ) ), ), - description="Compose two graphs in parallel.", + description="Merges two input `Graph`s into a single output `Graph` " + "with the disjoint union of their inputs and similarly their outputs.", input_order=["left", "right"], output_order=["value"], ), @@ -620,7 +627,8 @@ async def _parallel(_client, _stack, inputs: StructValue) -> StructValue: @namespace.function(type_vars={"a": StarKind()}) async def switch(pred: bool, if_true: a, if_false: a) -> a: - """Chooses a value depending on a boolean predicate.""" + """Passes one or other of two inputs through according to a \ +third, boolean, input.""" return if_true if pred else if_false @@ -632,7 +640,7 @@ class _UnpackPairOut(Generic[a, b], UnpackRow): @namespace.function(type_vars={"a": StarKind(), "b": StarKind()}) async def unpack_pair(pair: TierkreisPair[a, b]) -> _UnpackPairOut[a, b]: - "Unpacks a pair." + "Splits an input `Pair` into two separate outputs." return _UnpackPairOut(pair.first, pair.second) @@ -658,7 +666,8 @@ async def _unpack_struct(_client, _stack, ins: StructValue) -> StructValue: ) ), ), - description="Destructure a struct in to outgoing ports.", + description="Destructure a single `Struct` input, outputting one value " + "per field in the input, output ports matching field names.", input_order=["struct"], ), ) @@ -682,7 +691,8 @@ class GraphOut(Generic[b], UnpackRow): @namespace.function(name="map", type_vars={"a": StarKind(), "b": StarKind()}) async def _map(thunk: RuntimeGraph[GraphIn[a], GraphOut[b]], value: list[a]) -> list[b]: - """Runs a graph on each element of a Vec and collects the results in a Vec""" + """Runs a Graph input on each element of a `Vec` input and \ +collects the results into a `Vec` output.""" # We avoid the use of callbacks in the python builtins and so must # define map in the pyruntime raise NotImplementedError() diff --git a/python/tierkreis/pyruntime/python_runtime.py b/python/tierkreis/pyruntime/python_runtime.py index cab98c7..0e4968a 100644 --- a/python/tierkreis/pyruntime/python_runtime.py +++ b/python/tierkreis/pyruntime/python_runtime.py @@ -37,9 +37,9 @@ class _ValueNotFound(Exception): - def __init__(self, tke: TierkreisEdge) -> None: - self.edge = tke - super().__init__(f"Value not found on edge {tke.source} -> {tke.target}") + def __init__(self, edge: TierkreisEdge) -> None: + self.edge = edge + super().__init__(f"Value not found on edge {edge.source} -> {edge.target}") class OutputNotFound(_ValueNotFound): @@ -283,7 +283,7 @@ def can_type_check(self) -> bool: class VizRuntime(PyRuntime): """Child class of ``PyRuntime`` that can interact with a tierkreis-viz instance - for live graph visulization.""" + for live graph visualization.""" def __init__(self, url: str, roots: Iterable["Namespace"], num_workers: int = 1): """`url` is the address of the running tierkreis-viz instance. See @@ -303,7 +303,7 @@ def _post(self, endpoint: str, data): async def type_check_graph(self, graph: TierkreisGraph) -> TierkreisGraph: """See ``PyRuntime.type_check_graph``. Additionally updates - vizualized graph with type annotations.""" + visualized graph with type annotations.""" try: typedg = await super().type_check_graph(graph) except TierkreisTypeErrors as e: diff --git a/python/tierkreis/worker/prelude.py b/python/tierkreis/worker/prelude.py index 6248554..4784c95 100644 --- a/python/tierkreis/worker/prelude.py +++ b/python/tierkreis/worker/prelude.py @@ -46,7 +46,7 @@ def setup_tracing(service_name: str): def start_worker_server(worker_name: str, namespace: Namespace): """Set up tracing and run the worker server with the provided namespaces. - Expects a port specified on the command line, and reports succesful start to + Expects a port specified on the command line, and reports successful start to stdout """ diff --git a/tierkreis-core/Cargo.toml b/tierkreis-core/Cargo.toml new file mode 100644 index 0000000..5973654 --- /dev/null +++ b/tierkreis-core/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "tierkreis-core" +description = "Core implementation for the tierkreis quantum-classical hybrid workflow orchestration tool." +version = "0.1.0" +authors = [ + "Seyon Sivarajah ", + "Lukas Heidemann ", + "John Children ", + "Alan Lawrence ", +] +edition = { workspace = true } +rust-version = { workspace = true } +license = { workspace = true } +readme = "README.md" +homepage = { workspace = true } +repository = { workspace = true } + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +indenter = "0.3.3" +indexmap = "1.7.0" +itertools = "0.10.5" +lasso = { version = "0.5.1", features = ["multi-threaded"] } +once_cell = "1.8.0" +regex = "1.6.0" +thiserror = "1.0.28" +uuid = "1.2.1" + + +[dev-dependencies] +rstest.workspace = true +single = "0.1.0" + +[lints] +workspace = true diff --git a/tierkreis-core/README.md b/tierkreis-core/README.md new file mode 100644 index 0000000..57b0bde --- /dev/null +++ b/tierkreis-core/README.md @@ -0,0 +1,4 @@ +# tierkreis-core + + +Crate containing core types and functionality for the [tierkreis](https://github.com/CQCL/tierkreis) quantum-classical hybrid workflow orchestration tool. diff --git a/tierkreis-core/src/builtins.rs b/tierkreis-core/src/builtins.rs new file mode 100644 index 0000000..31c5532 --- /dev/null +++ b/tierkreis-core/src/builtins.rs @@ -0,0 +1,885 @@ +//! Contains a [FunctionDeclaration] for each builtin function, +//! all grouped together in [namespace]. +use std::collections::HashMap; + +// The [`SignatureEntry`]s for all builtin operations. +use crate::graph::{Constraint, GraphType, Kind, Type, TypeScheme}; +use crate::namespace::{FunctionDeclaration, Namespace}; +use crate::symbol::{Label, Name, TypeVar}; + +/// Description of [builtin_id]. +pub const DOCS_ID: &str = "Passes a single, arbitrary, value from input to output."; +/// Description of [builtin_sleep]. +pub const DOCS_SLEEP: &str = "Identity function with an asynchronous delay input in seconds."; +/// Description of [builtin_eval]. +pub const DOCS_EVAL: &str = "Evaluates the graph on the `thunk` input with other inputs \ + matching the graph inputs, producing outputs matching those of the graph."; +/// Description of [builtin_copy]. +pub const DOCS_COPY: &str = "Copies its input value to each of its two outputs."; +/// Description of [builtin_discard]. +pub const DOCS_DISCARD: &str = "Ignores its input value, has no outputs."; +/// Description of [builtin_equality]. +pub const DOCS_EQUALITY: &str = "Check two input values of the same type for equality, \ + producing a boolean."; +/// Description of [builtin_switch]. +pub const DOCS_SWITCH: &str = "Passes one or other of two inputs through according to a \ + third, boolean, input."; +/// Description of [builtin_make_pair]. +pub const DOCS_MAKE_PAIR: &str = "Makes an output `Pair` from two separate inputs."; +/// Description of [builtin_unpack_pair]. +pub const DOCS_UNPACK_PAIR: &str = "Splits an input `Pair` into two separate outputs."; +/// Description of [builtin_push]. +pub const DOCS_PUSH: &str = "Adds an input element onto an input `Vec` to give an output `Vec`."; +/// Description of [builtin_pop]. +pub const DOCS_POP: &str = "Pops the first element from an input `Vec`, returning said \ + element separately from the remainder `Vec`. Fails at runtime if the input `Vec` \ + is empty."; +/// Description of [builtin_loop]. +pub const DOCS_LOOP: &str = "Repeatedly applies a `Graph` input while it produces \ + a `Variant` tagged `continue`, i.e. until it returns a value tagged `break`, \ + and then returns that value."; +/// Description of [builtin_sequence]. +pub const DOCS_SEQUENCE: &str = + "Sequence two graphs, where the outputs of the first graph match the inputs of the second."; +/// Description of [builtin_make_struct]. +pub const DOCS_MAKE_STRUCT: &str = "Takes any number of inputs and produces a single \ + `Struct` output with a field for each input, field names matching input ports."; +/// Description of [builtin_unpack_struct]. +pub const DOCS_UNPACK_STRUCT: &str = "Destructure a single `Struct` input, outputting one value \ + per field in the input, output ports matching field names."; +/// Description of [builtin_insert_key]. +pub const DOCS_INSERT_KEY: &str = "Transforms an input `Map` into an output by adding an \ + entry (or replacing an existing one) for an input key and value."; +/// Description of [builtin_remove_key]. +pub const DOCS_REMOVE_KEY: &str = + "Remove a key (input) from a map (input), return the map and value."; +/// Description of [builtin_parallel]. +pub const DOCS_PARALLEL: &str = "Merges two input `Graph`s into a single output `Graph` \ + with the disjoint union of their inputs and similarly their outputs."; +/// Description of [builtin_map]. +pub const DOCS_MAP: &str = "Runs a Graph input on each element of a `Vec` input and \ + collects the results into a `Vec` output."; + +/// Declaration for the `id` function, see [DOCS_ID]. +pub fn builtin_id() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(Label::value(), a); + t.add_output(Label::value(), a); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_ID.to_string(), + input_order: vec![Label::value()], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `sleep` function, see [DOCS_SLEEP]. +pub fn builtin_sleep() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + + let delay = Label::symbol("delay_secs"); + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(Label::value(), a); + t.add_input(delay, Type::Float); + t.add_output(Label::value(), a); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_SLEEP.to_string(), + input_order: vec![Label::value(), delay], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `eval` function, see [DOCS_EVAL]. +pub fn builtin_eval() -> FunctionDeclaration { + let inv = TypeVar::symbol("in"); + let outv = TypeVar::symbol("out"); + + let type_scheme = TypeScheme::from({ + let mut thunk = GraphType::new(); + thunk.inputs.rest = Some(inv); + thunk.outputs.rest = Some(outv); + + let mut t = GraphType::new(); + t.add_input(Label::thunk(), Type::Graph(thunk)); + t.inputs.rest = Some(inv); + t.outputs.rest = Some(outv); + + t + }) + .with_variable(inv, Kind::Row) + .with_variable(outv, Kind::Row); + + FunctionDeclaration { + type_scheme, + description: DOCS_EVAL.to_string(), + input_order: vec![Label::thunk()], + output_order: vec![], + } +} + +/// Declaration for the `copy` function, see [DOCS_COPY]. +pub fn builtin_copy() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + let value_0 = Label::symbol("value_0"); + let value_1 = Label::symbol("value_1"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(Label::value(), a); + t.add_output(value_0, a); + t.add_output(value_1, a); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_COPY.to_string(), + input_order: vec![Label::value()], + output_order: vec![value_0, value_1], + } +} + +/// Declaration for the `discard` function, see [DOCS_DISCARD]. +pub fn builtin_discard() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(Label::value(), a); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_DISCARD.to_string(), + input_order: vec![Label::value()], + output_order: vec![], + } +} + +/// Declaration for the `eq` function, see [DOCS_EQUALITY]. +pub fn builtin_equality() -> FunctionDeclaration { + let val = TypeVar::symbol("val"); + let value_0 = Label::symbol("value_0"); + let value_1 = Label::symbol("value_1"); + let result = Label::symbol("result"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(value_0, val); + t.add_input(value_1, val); + t.add_output(result, Type::Bool); + t + }) + .with_variable(val, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_EQUALITY.to_string(), + input_order: vec![value_0, value_1], + output_order: vec![result], + } +} + +/// Declaration for the `"neq"` function, which compares two inputs of the same +/// type and returns `true` if-and-only-if they are different. +pub fn builtin_not_equality() -> FunctionDeclaration { + let val = TypeVar::symbol("val"); + let value_0 = Label::symbol("value_0"); + let value_1 = Label::symbol("value_1"); + let result = Label::symbol("result"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(value_0, val); + t.add_input(value_1, val); + t.add_output(result, Type::Bool); + t + }) + .with_variable(val, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: "Check two values are not equal.".into(), + input_order: vec![value_0, value_1], + output_order: vec![result], + } +} + +/// Declaration for the `switch` function, see [DOCS_SWITCH]. +pub fn builtin_switch() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + let pred = Label::symbol("pred"); + let if_true = Label::symbol("if_true"); + let if_false = Label::symbol("if_false"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(pred, Type::Bool); + t.add_input(if_true, a); + t.add_input(if_false, a); + t.add_output(Label::value(), a); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_SWITCH.to_string(), + input_order: vec![pred, if_true, if_false], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `make_pair` function, see [DOCS_MAKE_PAIR]. +pub fn builtin_make_pair() -> FunctionDeclaration { + let first = Label::symbol("first"); + let second = Label::symbol("second"); + let pair = Label::symbol("pair"); + let a = TypeVar::symbol("a"); + let b = TypeVar::symbol("b"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(first, a); + t.add_input(second, b); + t.add_output(pair, Type::Pair(Box::new(a.into()), Box::new(b.into()))); + t + }) + .with_variable(a, Kind::Star) + .with_variable(b, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_MAKE_PAIR.to_string(), + input_order: vec![first, second], + output_order: vec![pair], + } +} + +/// Declaration for the `unpack_pair` function, see [DOCS_UNPACK_PAIR]. +pub fn builtin_unpack_pair() -> FunctionDeclaration { + let first = Label::symbol("first"); + let second = Label::symbol("second"); + let pair = Label::symbol("pair"); + let a = TypeVar::symbol("a"); + let b = TypeVar::symbol("b"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(pair, Type::Pair(Box::new(a.into()), Box::new(b.into()))); + t.add_output(first, a); + t.add_output(second, b); + t + }) + .with_variable(a, Kind::Star) + .with_variable(b, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_UNPACK_PAIR.to_string(), + input_order: vec![pair], + output_order: vec![first, second], + } +} + +/// Declaration for the `push` function, see [DOCS_PUSH]. +pub fn builtin_push() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + let item = Label::symbol("item"); + let vec = Label::symbol("vec"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(vec, Type::Vec(Box::new(a.into()))); + t.add_input(item, a); + t.add_output(vec, Type::Vec(Box::new(a.into()))); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_PUSH.to_string(), + input_order: vec![vec, item], + output_order: vec![vec], + } +} + +/// Declaration for the `pop` function, see [DOCS_POP]. +pub fn builtin_pop() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + let item = Label::symbol("item"); + let vec = Label::symbol("vec"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(vec, Type::Vec(Box::new(a.into()))); + t.add_output(vec, Type::Vec(Box::new(a.into()))); + t.add_output(item, a); + t + }) + .with_variable(a, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_POP.to_string(), + input_order: vec![vec], + output_order: vec![vec, item], + } +} + +/// Declaration for the `loop` function, see [DOCS_LOOP]. +pub fn builtin_loop() -> FunctionDeclaration { + let loop_var = TypeVar::symbol("loop_var"); + let result = TypeVar::symbol("result"); + let body = Label::symbol("body"); + + let type_scheme = TypeScheme::from({ + use crate::graph::RowType; + use std::collections::BTreeMap; + let mut body_ty = GraphType::new(); + body_ty.add_input(Label::value(), loop_var); + + body_ty.add_output( + Label::value(), + Type::Variant(RowType { + content: BTreeMap::from([ + (Label::continue_(), Type::Var(loop_var)), + (Label::break_(), Type::Var(result)), + ]), + rest: None, + }), + ); + + let mut t = GraphType::new(); + t.add_input(body, Type::Graph(body_ty)); + t.add_input(Label::value(), loop_var); + t.add_output(Label::value(), result); + t + }) + .with_variable(loop_var, Kind::Star) + .with_variable(result, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_LOOP.to_string(), + input_order: vec![body, Label::value()], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `sequence` function, see [DOCS_SEQUENCE]. +pub fn builtin_sequence() -> FunctionDeclaration { + let inv = TypeVar::symbol("in"); + let middle = TypeVar::symbol("middle"); + let outv = TypeVar::symbol("out"); + let first = Label::symbol("first"); + let second = Label::symbol("second"); + let sequenced = Label::symbol("sequenced"); + + let type_scheme = TypeScheme::from({ + let mut g1 = GraphType::new(); + g1.inputs.rest = Some(inv); + g1.outputs.rest = Some(middle); + + let mut g2 = GraphType::new(); + g2.inputs.rest = Some(middle); + g2.outputs.rest = Some(outv); + + let mut g3 = GraphType::new(); + g3.inputs.rest = Some(inv); + g3.outputs.rest = Some(outv); + + let mut t = GraphType::new(); + t.add_input(first, Type::Graph(g1)); + t.add_input(second, Type::Graph(g2)); + t.add_output(sequenced, Type::Graph(g3)); + + t + }) + .with_variable(inv, Kind::Row) + .with_variable(middle, Kind::Row) + .with_variable(outv, Kind::Row); + + FunctionDeclaration { + type_scheme, + description: DOCS_SEQUENCE.to_string(), + input_order: vec![first, second], + output_order: vec![sequenced], + } +} + +/// Declaration for the `make_struct` function, see [DOCS_MAKE_STRUCT]. +pub fn builtin_make_struct() -> FunctionDeclaration { + let fields = TypeVar::symbol("fields"); + let structv = Label::symbol("struct"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.inputs.rest = Some(fields); + t.add_output(structv, Type::struct_from_row(fields.into())); + t + }) + .with_variable(fields, Kind::Row); + + FunctionDeclaration { + type_scheme, + description: DOCS_MAKE_STRUCT.to_string(), + input_order: vec![], + output_order: vec![structv], + } +} + +/// Declaration for the `unpack_struct` function, see [DOCS_UNPACK_STRUCT]. +pub fn builtin_unpack_struct() -> FunctionDeclaration { + let fields = TypeVar::symbol("fields"); + let structv = Label::symbol("struct"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.outputs.rest = Some(fields); + t.add_input(structv, Type::struct_from_row(fields.into())); + t + }) + .with_variable(fields, Kind::Row); + + FunctionDeclaration { + type_scheme, + description: DOCS_UNPACK_STRUCT.to_string(), + input_order: vec![structv], + output_order: vec![], + } +} + +/// Declaration for the `"insert_key"` function, see [DOCS_INSERT_KEY]. +pub fn builtin_insert_key() -> FunctionDeclaration { + let keytype = TypeVar::symbol("a"); + let valtype = TypeVar::symbol("b"); + let maptype = Type::Map(Box::new(keytype.into()), Box::new(valtype.into())); + let map = Label::symbol("map"); + let key = Label::symbol("key"); + let val = Label::symbol("val"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(map, maptype.clone()); + t.add_input(key, keytype); + t.add_input(val, valtype); + t.add_output(map, maptype); + t + }) + .with_variable(keytype, Kind::Star) + .with_variable(valtype, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_INSERT_KEY.to_string(), + input_order: vec![map, key, val], + output_order: vec![map], + } +} + +/// Declaration for the `"remove_key"` function, see [DOCS_REMOVE_KEY]. +pub fn builtin_remove_key() -> FunctionDeclaration { + let keytype = TypeVar::symbol("a"); + let valtype = TypeVar::symbol("b"); + let maptype = Type::Map(Box::new(keytype.into()), Box::new(valtype.into())); + let map = Label::symbol("map"); + let key = Label::symbol("key"); + let val = Label::symbol("val"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(map, maptype.clone()); + t.add_input(key, keytype); + t.add_output(map, maptype); + t.add_output(val, valtype); + t + }) + .with_variable(keytype, Kind::Star) + .with_variable(valtype, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_REMOVE_KEY.to_string(), + input_order: vec![map, key], + output_order: vec![map, val], + } +} + +fn gen_binary_decl( + in_type_: Type, + out_type_: Type, + name: &'static str, + docs: &str, +) -> (Name, FunctionDeclaration) { + let a = Label::symbol("a"); + let b = Label::symbol("b"); + + let typescheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(a, in_type_.clone()); + t.add_input(b, in_type_); + t.add_output(Label::value(), out_type_); + t + }); + + let decl = FunctionDeclaration { + type_scheme: typescheme, + description: docs.into(), + input_order: vec![a, b], + output_order: vec![Label::value()], + }; + + (Name::symbol(name), decl) +} + +/// Declaration for the `"int_to_float"` function +pub fn builtin_int_to_float() -> FunctionDeclaration { + let int = Label::symbol("int"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(int, Type::Int); + t.add_output(Label::value(), Type::Float); + t + }); + + FunctionDeclaration { + type_scheme, + description: "Convert an integer to a float".into(), + input_order: vec![int], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `"float_to_int"` function +pub fn builtin_float_to_int() -> FunctionDeclaration { + let float = Label::symbol("float"); + + let type_scheme = TypeScheme::from({ + let mut t = GraphType::new(); + t.add_input(float, Type::Float); + t.add_output(Label::value(), Type::Int); + t + }); + + FunctionDeclaration { + type_scheme, + description: "Convert a float to an integer".into(), + input_order: vec![float], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `"parallel"` function, see [DOCS_PARALLEL]. +pub fn builtin_parallel() -> FunctionDeclaration { + let left_in = TypeVar::symbol("left_in"); + let right_in = TypeVar::symbol("right_in"); + let value_in = TypeVar::symbol("value_in"); + let left_out = TypeVar::symbol("left_out"); + let right_out = TypeVar::symbol("right_out"); + let value_out = TypeVar::symbol("value_out"); + let left = Label::symbol("left"); + let right = Label::symbol("right"); + let value = Label::symbol("value"); + + let partition_in = Constraint::Partition { + left: left_in.into(), + right: right_in.into(), + union: value_in.into(), + }; + + let partition_out = Constraint::Partition { + left: left_out.into(), + right: right_out.into(), + union: value_out.into(), + }; + + let type_scheme = TypeScheme::from({ + let mut left_ty = GraphType::new(); + left_ty.inputs.rest = Some(left_in); + left_ty.outputs.rest = Some(left_out); + + let mut right_ty = GraphType::new(); + right_ty.inputs.rest = Some(right_in); + right_ty.outputs.rest = Some(right_out); + + let mut value_ty = GraphType::new(); + value_ty.inputs.rest = Some(value_in); + value_ty.outputs.rest = Some(value_out); + + let mut t = GraphType::new(); + t.add_input(left, left_ty); + t.add_input(right, right_ty); + t.add_output(value, value_ty); + t + }) + .with_variable(left_in, Kind::Row) + .with_variable(left_out, Kind::Row) + .with_variable(right_in, Kind::Row) + .with_variable(right_out, Kind::Row) + .with_variable(value_in, Kind::Row) + .with_variable(value_out, Kind::Row) + .with_constraint(partition_in) + .with_constraint(partition_out); + + FunctionDeclaration { + type_scheme, + description: DOCS_PARALLEL.to_string(), + input_order: vec![left, right], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `"partial"` function i.e. that partially-applies a +/// [Value::Graph] to some of its arguments +/// +/// [Value::Graph]: crate::graph::Value::Graph +pub fn builtin_partial() -> FunctionDeclaration { + let in_given = TypeVar::symbol("in_given"); + let in_rest = TypeVar::symbol("in_rest"); + let inv = TypeVar::symbol("in"); + let outv = TypeVar::symbol("outv"); + + let partition_in = Constraint::Partition { + left: Type::Var(in_given), + right: Type::Var(in_rest), + union: Type::Var(inv), + }; + + let type_scheme = TypeScheme::from({ + let mut thunk = GraphType::new(); + thunk.inputs.rest = Some(inv); + thunk.outputs.rest = Some(outv); + + let mut value = GraphType::new(); + value.inputs.rest = Some(in_rest); + value.outputs.rest = Some(outv); + + let mut t = GraphType::new(); + t.add_input(Label::thunk(), thunk); + t.inputs.rest = Some(in_given); + t.add_output(Label::value(), value); + t + }) + .with_variable(in_given, Kind::Row) + .with_variable(in_rest, Kind::Row) + .with_variable(inv, Kind::Row) + .with_variable(outv, Kind::Row) + .with_constraint(partition_in); + + FunctionDeclaration { + type_scheme, + description: + "Partial application; output a new graph with some input values injected as constants" + .to_string(), + input_order: vec![Label::thunk()], + output_order: vec![Label::value()], + } +} + +/// Declaration for the `map` function, see [DOCS_MAP]. +pub fn builtin_map() -> FunctionDeclaration { + let a = TypeVar::symbol("a"); + let b = TypeVar::symbol("b"); + + let type_scheme = TypeScheme::from({ + let mut thunk = GraphType::new(); + thunk.add_input(Label::value(), a); + thunk.add_output(Label::value(), b); + + let mut t = GraphType::new(); + t.add_input(Label::value(), Type::Vec(Box::new(Type::Var(a)))); + t.add_input(Label::thunk(), Type::Graph(thunk)); + t.add_output(Label::value(), Type::Vec(Box::new(Type::Var(b)))); + t + }) + .with_variable(a, Kind::Star) + .with_variable(b, Kind::Star); + + FunctionDeclaration { + type_scheme, + description: DOCS_MAP.to_string(), + input_order: vec![Label::thunk(), Label::value()], + output_order: vec![Label::value()], + } +} + +/// The namespace containing a [FunctionDeclaration] for each builtin function +pub fn namespace() -> Namespace { + let decs = HashMap::from([ + (Name::symbol("id"), builtin_id()), + (Name::symbol("sleep"), builtin_sleep()), + (Name::symbol("eval"), builtin_eval()), + (Name::symbol("make_pair"), builtin_make_pair()), + (Name::symbol("unpack_pair"), builtin_unpack_pair()), + (Name::symbol("switch"), builtin_switch()), + (Name::symbol("copy"), builtin_copy()), + (Name::symbol("discard"), builtin_discard()), + (Name::symbol("eq"), builtin_equality()), + (Name::symbol("neq"), builtin_not_equality()), + (Name::symbol("push"), builtin_push()), + (Name::symbol("pop"), builtin_pop()), + (Name::symbol("loop"), builtin_loop()), + (Name::symbol("sequence"), builtin_sequence()), + (Name::symbol("make_struct"), builtin_make_struct()), + (Name::symbol("unpack_struct"), builtin_unpack_struct()), + (Name::symbol("insert_key"), builtin_insert_key()), + (Name::symbol("remove_key"), builtin_remove_key()), + (Name::symbol("int_to_float"), builtin_int_to_float()), + (Name::symbol("float_to_int"), builtin_float_to_int()), + (Name::symbol("partial"), builtin_partial()), + (Name::symbol("parallel"), builtin_parallel()), + (Name::symbol("map"), builtin_map()), + gen_binary_decl( + Type::Int, + Type::Int, + "iadd", + "Add integers a and b together; a + b", + ), + gen_binary_decl( + Type::Int, + Type::Int, + "isub", + "Subtract integer b from integer a; a - b", + ), + gen_binary_decl( + Type::Int, + Type::Int, + "imul", + "Multiply integers a and b together; a * b", + ), + gen_binary_decl( + Type::Int, + Type::Int, + "idiv", + "Integer division of a by b; a / b", + ), + gen_binary_decl(Type::Int, Type::Int, "imod", "Modulo of a by b; a % b"), + gen_binary_decl( + Type::Int, + Type::Int, + "ipow", + "Exponentiate a by power b; a ^ b. b must be a positive integer.", + ), + gen_binary_decl( + Type::Float, + Type::Float, + "fadd", + "Add floats a and b together; a + b", + ), + gen_binary_decl( + Type::Float, + Type::Float, + "fsub", + "Subtract float b from float a; a - b", + ), + gen_binary_decl( + Type::Float, + Type::Float, + "fmul", + "Multiply floats a and b together; a * b", + ), + gen_binary_decl( + Type::Float, + Type::Float, + "fdiv", + "float division of a by b; a / b", + ), + gen_binary_decl(Type::Float, Type::Float, "fmod", "Modulo of a by b; a % b"), + gen_binary_decl( + Type::Float, + Type::Float, + "fpow", + "Exponentiate a by power b; a ^ b", + ), + gen_binary_decl( + Type::Int, + Type::Bool, + "ilt", + "Check if a is less than b; a < b", + ), + gen_binary_decl( + Type::Int, + Type::Bool, + "ileq", + "Check if a is less than or equal to b; a <= b", + ), + gen_binary_decl( + Type::Int, + Type::Bool, + "igt", + "Check if a is greater than b; a > b", + ), + gen_binary_decl( + Type::Int, + Type::Bool, + "igeq", + "Check if a is greater than or equal to b; a >= b", + ), + gen_binary_decl( + Type::Float, + Type::Bool, + "flt", + "Check if a is less than b; a < b", + ), + gen_binary_decl( + Type::Float, + Type::Bool, + "fleq", + "Check if a is less than or equal to b; a <= b", + ), + gen_binary_decl( + Type::Float, + Type::Bool, + "fgt", + "Check if a is greater than b; a > b", + ), + gen_binary_decl( + Type::Float, + Type::Bool, + "fgeq", + "Check if a is greater than or equal to b; a >= b", + ), + gen_binary_decl( + Type::Bool, + Type::Bool, + "and", + "Check a and b are true; a && b", + ), + gen_binary_decl( + Type::Bool, + Type::Bool, + "or", + "Check a or b are true; a || b", + ), + gen_binary_decl( + Type::Bool, + Type::Bool, + "xor", + "Check either a or b are true; a ^ b", + ), + ]); + + Namespace { + functions: decs, + subspaces: HashMap::new(), + } +} diff --git a/tierkreis-core/src/graph.rs b/tierkreis-core/src/graph.rs new file mode 100644 index 0000000..11b3a3f --- /dev/null +++ b/tierkreis-core/src/graph.rs @@ -0,0 +1,962 @@ +//! Crate versions of graph protobufs, plus [GraphBuilder] +use super::portgraph::graph::{ConnectError, Direction}; +use crate::prelude::{TryFrom, TryInto}; +use crate::symbol::{FunctionName, Label, Location, SymbolError, TypeVar}; +use indexmap::{IndexMap, IndexSet}; + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::convert::Infallible; +use std::hash::{Hash, Hasher}; +use thiserror::Error; + +pub use super::portgraph::graph::{EdgeIndex, NodeIndex}; + +/// A value that can be passed around a Tierkreis graph. +/// A strict (no optional fields), crate version of protobuf `tierkreis.v1alpha.graph.Value`. +#[derive(Clone, Debug)] +pub enum Value { + /// Boolean value (true or false) + Bool(bool), + /// Signed integer + Int(i64), + /// String + Str(String), + /// Double-precision (64-bit) float + Float(f64), + /// A Tierkreis graph + Graph(Graph), + /// A pair of two other [Value]s + Pair(Box<(Value, Value)>), + /// A map from keys to values. Keys must be hashable, i.e. must not be or contain + /// [Value::Graph], [Value::Map], [Value::Struct] or [Value::Float]. + Map(HashMap), + /// List of [Value]s. + Vec(Vec), + /// A struct or record type with string-named fields (themselves [Value]s) + Struct(HashMap), + /// A [Value] tagged with a string to make a disjoint union of component types + Variant(Label, Box), +} + +impl PartialEq for Value { + fn eq(&self, other: &Value) -> bool { + match (self, other) { + (Value::Bool(x), Value::Bool(y)) => x == y, + (Value::Int(x), Value::Int(y)) => x == y, + (Value::Str(x), Value::Str(y)) => x == y, + (Value::Float(x), Value::Float(y)) => x == y, + (Value::Pair(x), Value::Pair(y)) => x == y, + (Value::Map(x), Value::Map(y)) => x == y, + (Value::Struct(f1), Value::Struct(f2)) => f1 == f2, + (Value::Vec(ar1), Value::Vec(ar2)) => ar1 == ar2, + (Value::Graph(x), Value::Graph(y)) => x == y, + (Value::Variant(t1, v1), Value::Variant(t2, v2)) => (t1 == t2) && (v1 == v2), + _ => false, + } + } +} + +// Required since we use Value's as the *keys* of HashMap's +impl Eq for Value {} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + match self { + Value::Graph(_) | Value::Map(_) | Value::Struct(_) | Value::Float(_) => { + panic!("Value is not hashable: {:?}", self) + } + Value::Bool(x) => x.hash(state), + Value::Int(x) => x.hash(state), + Value::Str(x) => x.hash(state), + Value::Pair(x) => x.hash(state), + Value::Vec(x) => x.hash(state), + Value::Variant(tag, value) => { + tag.hash(state); + value.hash(state); + } + } + } +} + +/// Crate version of protobuf `tierkreis.v1alpha1.graph.Type`. +/// A type of values (or [Row](Type::Row)s) that can be passed around a Tierkreis [Graph]. +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum Type { + /// Type ([Kind::Star]) of Booleans, i.e. with two values `true` and `false` + Bool, + /// Type ([Kind::Star]) of signed integers + Int, + /// Type ([Kind::Star]) of strings + Str, + /// Type ([Kind::Star]) of floating-point numbers (double-precision) + Float, + /// Type ([Kind::Star]) identifying the set of graphs with the specified + /// row of named inputs and row of named outputs + Graph(GraphType), + /// Type ([Kind::Star]) of pairs (where the first value has one type, and + /// the second another); each component must also be a [Kind::Star]. + Pair(Box, Box), + /// Type ([Kind::Star]) of lists of elements all of the same type (given, + /// also [Kind::Star]). + Vec(Box), + /// Type variable, used (in types) inside polymorphic [TypeScheme]s only. + /// Can be a [Kind::Row] or a [Kind::Star]. + Var(TypeVar), + /// A named row of types. Unlike the other variants (except possibly [Type::Var]), + /// this is a ([Kind::Row])), *not* a type of values, so cannot be used as a member + /// of any other [Type] (e.g. [Type::Pair], [Type::Vec]), or as the type of a field + /// in a [RowType]. + /// However, can appear in [Constraint::Lacks::row] or [Constraint::Partition], + /// and can be returned in [TypeError::Unify] + /// + /// [TypeError::Unify]: super::type_checker::TypeError::Unify + Row(RowType), + /// Type ([Kind::Star]) of maps from a key type to value type (both [Kind::Star]). + // We do nothing to rule out key *types* that are not hashable, only values + Map(Box, Box), + /// Struct type (i.e. [Kind::Star]): made up of an unordered collection of named + /// fields each with a type ([Kind::Star]). Optionally, the type itself may have + /// a name. + Struct(RowType, Option), + /// 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). + Variant(RowType), +} + +impl Type { + /// Makes an unnamed [Type::Struct] given a row of fields + pub fn struct_from_row(row: RowType) -> Self { + Self::Struct(row, None) + } +} + +impl std::fmt::Debug for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Bool => f.debug_struct("Bool").finish(), + Type::Int => f.debug_struct("Int").finish(), + Type::Str => f.debug_struct("Str").finish(), + Type::Float => f.debug_struct("Float").finish(), + Type::Graph(graph) => std::fmt::Debug::fmt(graph, f), + Type::Pair(first, second) => f.debug_tuple("Pair").field(first).field(second).finish(), + Type::Vec(element) => f.debug_tuple("Vector").field(element).finish(), + Type::Var(var) => std::fmt::Debug::fmt(var, f), + Type::Row(row) => f.debug_tuple("Row").field(row).finish(), + Type::Map(key, value) => f.debug_tuple("Map").field(key).field(value).finish(), + Type::Struct(row, name) => match name { + Some(name) => f.debug_struct(name).finish(), + None => f.debug_tuple("Struct").field(row).finish(), + }, + Type::Variant(row) => f.debug_tuple("Variant").field(row).finish(), + } + } +} + +impl From for Type { + fn from(t: GraphType) -> Self { + Type::Graph(t) + } +} + +impl From for Type { + fn from(t: TypeVar) -> Self { + Type::Var(t) + } +} + +impl Type { + /// Iterator over the type variables in the type in the order they occur. + /// Each variable is returned only once. + pub fn type_vars(&self) -> impl Iterator + '_ { + let mut vars = IndexSet::new(); + self.type_vars_impl(&mut vars); + vars.into_iter() + } + + fn type_vars_impl(&self, vars: &mut IndexSet) { + match self { + Type::Bool => {} + Type::Int => {} + Type::Str => {} + Type::Float => {} + Type::Graph(graph) => { + graph.type_vars_impl(vars); + } + Type::Pair(left, right) => { + left.type_vars_impl(vars); + right.type_vars_impl(vars); + } + Type::Vec(element) => { + element.type_vars_impl(vars); + } + Type::Var(var) => { + vars.insert(*var); + } + Type::Row(row) => { + row.type_vars_impl(vars); + } + Type::Map(key, value) => { + key.type_vars_impl(vars); + value.type_vars_impl(vars); + } + Type::Struct(row, _) => { + row.type_vars_impl(vars); + } + Type::Variant(row) => { + row.type_vars_impl(vars); + } + } + } +} + +/// Type of a Graph, i.e. a higher-order function value, with input and output rows. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct GraphType { + /// The inputs to the graph (known and/or variable) + pub inputs: RowType, + /// The outputs from the graph (known and/or variable) + pub outputs: RowType, +} + +impl GraphType { + /// Creates a new instance with closed empty input and output rows + pub fn new() -> Self { + Self { + inputs: Default::default(), + outputs: Default::default(), + } + } + + /// Adds a new input given a label and type + pub fn add_input(&mut self, port: impl Into