├── tests ├── __init__.py ├── api │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── signing_test.py │ │ └── client_test.py │ └── app │ │ └── client_test.py └── harness.py ├── holochain_client ├── __init__.py └── api │ ├── __init__.py │ ├── admin │ ├── __init__.py │ ├── types.py │ └── client.py │ ├── app │ ├── __init__.py │ ├── types.py │ └── client.py │ └── common │ ├── __init__.py │ ├── types.py │ ├── request.py │ ├── pending_request_pool.py │ └── signing.py ├── fixture ├── workdir │ ├── web-happ.yaml │ └── happ.yaml ├── dnas │ └── fixture │ │ ├── zomes │ │ ├── coordinator │ │ │ └── fixture │ │ │ │ ├── src │ │ │ │ ├── all_fixtures.rs │ │ │ │ ├── lib.rs │ │ │ │ └── fixture.rs │ │ │ │ └── Cargo.toml │ │ └── integrity │ │ │ └── fixture │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ ├── fixture.rs │ │ │ └── lib.rs │ │ └── workdir │ │ └── dna.yaml ├── package.json ├── Cargo.toml └── package-lock.json ├── CHANGELOG.md ├── pyproject.toml ├── flake.nix ├── .github └── workflows │ └── test.yaml ├── README.md ├── .gitignore ├── flake.lock ├── LICENSE └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /holochain_client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/admin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /holochain_client/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /holochain_client/api/admin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /holochain_client/api/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /holochain_client/api/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fixture/workdir/web-happ.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: fixture 4 | ui: 5 | bundled: "../ui/dist.zip" 6 | happ_manifest: 7 | bundled: "./fixture.happ" 8 | -------------------------------------------------------------------------------- /holochain_client/api/common/types.py: -------------------------------------------------------------------------------- 1 | DnaHash = bytes 2 | AgentPubKey = bytes 3 | CellId = [DnaHash, AgentPubKey] 4 | ZomeName = str 5 | FunctionName = str 6 | RoleName = str 7 | MembraneProof = bytes 8 | NetworkSeed = str 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/coordinator/fixture/src/all_fixtures.rs: -------------------------------------------------------------------------------- 1 | use hdk::prelude::*; 2 | use fixture_integrity::*; 3 | #[hdk_extern] 4 | pub fn get_all_fixtures(_: ()) -> ExternResult> { 5 | let path = Path::from("all_fixtures"); 6 | get_links(path.path_entry_hash()?, LinkTypes::AllFixtures, None) 7 | } 8 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/integrity/fixture/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fixture_integrity" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | name = "fixture_integrity" 9 | 10 | [dependencies] 11 | hdi = { workspace = true } 12 | 13 | serde = { workspace = true } 14 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/coordinator/fixture/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fixture" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | name = "fixture" 9 | 10 | [dependencies] 11 | hdk = { workspace = true } 12 | 13 | serde = { workspace = true } 14 | 15 | fixture_integrity = { workspace = true } 16 | -------------------------------------------------------------------------------- /fixture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture-dev", 3 | "private": true, 4 | "scripts": { 5 | "build:happ": "npm run build:zomes && hc app pack workdir --recursive", 6 | "build:zomes": "RUSTFLAGS='' CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown" 7 | }, 8 | "engines": { 9 | "npm": ">=7.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fixture/workdir/happ.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: fixture 4 | description: ~ 5 | roles: 6 | - name: fixture 7 | provisioning: 8 | strategy: create 9 | deferred: false 10 | dna: 11 | bundled: "../dnas/fixture/workdir/fixture.dna" 12 | modifiers: 13 | network_seed: ~ 14 | properties: ~ 15 | origin_time: ~ 16 | quantum_time: ~ 17 | installed_hash: ~ 18 | clone_limit: 0 19 | -------------------------------------------------------------------------------- /fixture/Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.dev] 2 | opt-level = "z" 3 | 4 | [profile.release] 5 | opt-level = "z" 6 | 7 | [workspace] 8 | members = ["dnas/*/zomes/coordinator/*", "dnas/*/zomes/integrity/*"] 9 | 10 | [workspace.dependencies] 11 | hdi = "=0.3.5" 12 | hdk = "=0.2.5" 13 | serde = "=1.0.166" 14 | 15 | [workspace.dependencies.fixture] 16 | path = "dnas/fixture/zomes/coordinator/fixture" 17 | 18 | [workspace.dependencies.fixture_integrity] 19 | path = "dnas/fixture/zomes/integrity/fixture" 20 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/workdir/dna.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: fixture 4 | integrity: 5 | network_seed: ~ 6 | properties: ~ 7 | origin_time: 1707241096195813 8 | zomes: 9 | - name: fixture_integrity 10 | hash: ~ 11 | bundled: "../../../target/wasm32-unknown-unknown/release/fixture_integrity.wasm" 12 | dependencies: ~ 13 | dylib: ~ 14 | coordinator: 15 | zomes: 16 | - name: fixture 17 | hash: ~ 18 | bundled: "../../../target/wasm32-unknown-unknown/release/fixture.wasm" 19 | dependencies: 20 | - name: fixture_integrity 21 | dylib: ~ 22 | -------------------------------------------------------------------------------- /holochain_client/api/app/types.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | from typing import Any 3 | 4 | from holochain_client.api.common.types import ( 5 | AgentPubKey, 6 | CellId, 7 | FunctionName, 8 | ZomeName, 9 | ) 10 | 11 | 12 | @dataclasses.dataclass 13 | class ZomeCallUnsigned: 14 | cell_id: CellId 15 | zome_name: ZomeName 16 | fn_name: FunctionName 17 | payload: Any 18 | 19 | 20 | @dataclasses.dataclass 21 | class CallZome: 22 | cell_id: CellId 23 | zome_name: ZomeName 24 | fn_name: FunctionName 25 | payload: bytes 26 | provenance: AgentPubKey 27 | signature: bytes 28 | nonce: bytes 29 | """Microseconds from unix epoch""" 30 | expires_at: int 31 | cap_secret: bytes 32 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "holochain-client" 3 | version = "0.1.0" 4 | description = "A Python client for the Holochain Conductor API" 5 | authors = [ 6 | "ThetaSinner ", 7 | ] 8 | readme = "README.md" 9 | license = "CAL 1.0" 10 | keywords = ["holochain", "conductor-api"] 11 | include = ["CHANGELOG.md"] 12 | 13 | packages = [ 14 | { include = "holochain_client" } 15 | ] 16 | 17 | [tool.poetry.dependencies] 18 | python = "^3.8" 19 | msgpack = "^1.0.7" 20 | websockets = "^12.0" 21 | holochain-serialization = "^0.1.0" 22 | cryptography = "^42.0.2" 23 | ruff = "^0.2.1" 24 | 25 | [tool.poetry.group.test.dependencies] 26 | pytest = "^7.0.0" 27 | 28 | 29 | [tool.poetry.group.dev.dependencies] 30 | pytest-asyncio = "^0.23.4" 31 | 32 | [build-system] 33 | requires = ["poetry-core"] 34 | build-backend = "poetry.core.masonry.api" 35 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Flake for Holochain client development"; 3 | 4 | inputs = { 5 | versions.url = "github:holochain/holochain?dir=versions/0_2"; 6 | 7 | versions.inputs.holochain.url = "github:holochain/holochain/holochain-0.2.6"; 8 | 9 | holochain = { 10 | url = "github:holochain/holochain"; 11 | inputs.versions.follows = "versions"; 12 | }; 13 | 14 | nixpkgs.follows = "holochain/nixpkgs"; 15 | }; 16 | 17 | outputs = inputs @ { ... }: 18 | inputs.holochain.inputs.flake-parts.lib.mkFlake { inherit inputs; } 19 | { 20 | systems = builtins.attrNames inputs.holochain.devShells; 21 | perSystem = { config, pkgs, system, ... }: { 22 | devShells.default = pkgs.mkShell { 23 | inputsFrom = [ 24 | inputs.holochain.devShells.${system}.holonix 25 | ]; 26 | packages = [ 27 | (pkgs.python3.withPackages (python-pkgs: [ 28 | python-pkgs.pip 29 | python-pkgs.poetry-core 30 | ])) 31 | pkgs.poetry 32 | pkgs.nodejs_20 33 | ]; 34 | }; 35 | }; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /tests/api/admin/signing_test.py: -------------------------------------------------------------------------------- 1 | from holochain_client.api.common.signing import ( 2 | authorize_signing_credentials, 3 | generate_signing_keypair, 4 | ) 5 | import pytest 6 | from holochain_client.api.admin.types import EnableApp, InstallApp 7 | 8 | from tests.harness import TestHarness 9 | 10 | 11 | def test_generate_signing_keypair(): 12 | signing_keypair = generate_signing_keypair() 13 | assert len(signing_keypair.identity) == 39 14 | 15 | 16 | @pytest.mark.asyncio 17 | async def test_authorize_signing_credentials(): 18 | async with TestHarness() as harness: 19 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 20 | 21 | response = await harness.admin_client.install_app( 22 | InstallApp( 23 | agent_key=agent_pub_key, 24 | installed_app_id="test_app", 25 | path=harness.fixture_path, 26 | ) 27 | ) 28 | 29 | await harness.admin_client.enable_app( 30 | EnableApp(installed_app_id=response.installed_app_id) 31 | ) 32 | 33 | await authorize_signing_credentials( 34 | harness.admin_client, 35 | response.cell_info["fixture"][0]["provisioned"]["cell_id"], 36 | None, 37 | ) 38 | 39 | assert response.installed_app_id == "test_app" 40 | -------------------------------------------------------------------------------- /holochain_client/api/common/request.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from holochain_client.api.admin.types import ( 3 | AdminRequest, 4 | WireMessageRequest, 5 | ) 6 | import msgpack 7 | import dataclasses 8 | import re 9 | 10 | 11 | def tag_from_type(req: Any) -> str: 12 | # TODO compile 13 | return re.sub(r"(? bytes: 17 | if isinstance(req, list): 18 | data = [dataclasses.asdict(x) for x in req] 19 | elif isinstance(req, bytes): 20 | data = req 21 | else: 22 | data = dataclasses.asdict(req) 23 | 24 | if len(data) == 0: 25 | # If there is no data, we should send None instead of an empty dict 26 | data = None 27 | elif isinstance(data, dict): 28 | # Remove any None values from the data, no need to send fields without values 29 | data = {k: v for k, v in data.items() if v is not None} 30 | req = AdminRequest(tag, data) 31 | # print("Sending request: ", req) # TODO logging 32 | return msgpack.packb(dataclasses.asdict(req)) 33 | 34 | 35 | def create_wire_message_request(req: Any, tag: str, requestId: int) -> bytes: 36 | data = _create_request(req, tag) 37 | msg = WireMessageRequest(id=requestId, data=[x for x in data]) 38 | return msgpack.packb(dataclasses.asdict(msg)) 39 | -------------------------------------------------------------------------------- /holochain_client/api/common/pending_request_pool.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | import asyncio 3 | import websockets 4 | import msgpack 5 | import dataclasses 6 | 7 | 8 | @dataclasses.dataclass 9 | class PendingRequest: 10 | id: int 11 | trigger: asyncio.Event 12 | 13 | 14 | class PendingRequestPool: 15 | _requests: Dict[int, PendingRequest] = {} 16 | _responses: Dict[int, Any] = {} 17 | 18 | def __init__(self, client: websockets.WebSocketClientProtocol): 19 | asyncio.create_task(self._handle_responses(client)) 20 | 21 | def add(self, id: int, trigger: asyncio.Event): 22 | self._requests[id] = PendingRequest(id, trigger) 23 | 24 | async def _handle_responses(self, client: websockets.WebSocketClientProtocol): 25 | async for message in client: 26 | data = msgpack.unpackb(message) 27 | if "id" in data and "type" in data and data["type"] == "response": 28 | id = data["id"] 29 | if id in self._requests: 30 | data["data"] = msgpack.unpackb(data["data"]) 31 | self._responses[id] = data 32 | self._requests[id].trigger.set() 33 | del self._requests[id] 34 | else: 35 | # TODO logging 36 | print(f"Received response for unknown request id: {id}") 37 | 38 | def take_response(self, id: int) -> Any: 39 | return self._responses.pop(id)["data"] 40 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "main" 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref_name }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest] 18 | fail-fast: false 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Check out source code 23 | uses: actions/checkout@v4 24 | 25 | - name: Install nix 26 | uses: cachix/install-nix-action@v25 27 | with: 28 | install_url: https://releases.nixos.org/nix/nix-2.20.1/install 29 | 30 | - name: Set up cachix 31 | uses: cachix/cachix-action@v14 32 | with: 33 | name: holochain-ci 34 | 35 | - name: Build fixture 36 | run: nix develop -c $SHELL -c "cd fixture && npm install && npm run build:happ" 37 | 38 | - name: Prepare environment 39 | run: nix develop -c $SHELL -c "python -m venv .venv && source .venv/bin/activate && poetry install --no-root" 40 | 41 | - name: Check formatting 42 | run: nix develop -c $SHELL -c "source .venv/bin/activate && poetry run ruff format --diff" 43 | 44 | - name: Lint 45 | run: nix develop -c $SHELL -c "source .venv/bin/activate && poetry run ruff check" 46 | 47 | - name: Run tests 48 | run: nix develop -c $SHELL -c "source .venv/bin/activate && poetry run pytest" 49 | -------------------------------------------------------------------------------- /tests/api/app/client_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from holochain_client.api.admin.types import ( 3 | EnableApp, 4 | InstallApp, 5 | ) 6 | from holochain_client.api.app.types import ZomeCallUnsigned 7 | from holochain_client.api.common.signing import authorize_signing_credentials 8 | from tests.harness import TestHarness 9 | import msgpack 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_call_zome(): 14 | async with TestHarness() as harness: 15 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 16 | 17 | app_info = await harness.admin_client.install_app( 18 | InstallApp( 19 | agent_key=agent_pub_key, 20 | installed_app_id="test_app", 21 | path=harness.fixture_path, 22 | ) 23 | ) 24 | 25 | await harness.admin_client.enable_app(EnableApp(app_info.installed_app_id)) 26 | 27 | cell_id = app_info.cell_info["fixture"][0]["provisioned"]["cell_id"] 28 | await authorize_signing_credentials(harness.admin_client, cell_id) 29 | 30 | response = await harness.app_client.call_zome( 31 | ZomeCallUnsigned( 32 | cell_id=cell_id, 33 | zome_name="fixture", 34 | fn_name="create_fixture", 35 | payload=msgpack.packb({"name": "hello fixture"}), 36 | ) 37 | ) 38 | 39 | response_struct = msgpack.unpackb(response) 40 | assert ( 41 | response_struct["signed_action"]["hashed"]["content"]["author"] 42 | == agent_pub_key 43 | ) 44 | -------------------------------------------------------------------------------- /fixture/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixture-dev", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "fixture-dev", 8 | "engines": { 9 | "npm": ">=7.0.0" 10 | } 11 | }, 12 | "tests": { 13 | "extraneous": true, 14 | "dependencies": { 15 | "@holochain/client": "^0.16.5", 16 | "@holochain/tryorama": "^0.15.2", 17 | "@msgpack/msgpack": "^2.7.0", 18 | "typescript": "^4.9.4", 19 | "vitest": "^0.28.4" 20 | } 21 | }, 22 | "ui": { 23 | "version": "0.1.0", 24 | "extraneous": true, 25 | "dependencies": { 26 | "@holochain/client": "^0.16.5", 27 | "@material/mwc-button": "^0.27.0", 28 | "@material/mwc-checkbox": "^0.27.0", 29 | "@material/mwc-circular-progress": "^0.27.0", 30 | "@material/mwc-formfield": "^0.27.0", 31 | "@material/mwc-icon-button": "^0.27.0", 32 | "@material/mwc-select": "^0.27.0", 33 | "@material/mwc-slider": "^0.27.0", 34 | "@material/mwc-snackbar": "^0.27.0", 35 | "@material/mwc-textarea": "^0.27.0", 36 | "@material/mwc-textfield": "^0.27.0", 37 | "@msgpack/msgpack": "^2.7.2", 38 | "@vaadin/date-time-picker": "^23.2.8", 39 | "vue": "^3.2.25" 40 | }, 41 | "devDependencies": { 42 | "@vitejs/plugin-vue": "^4.0.0", 43 | "bestzip": "^2.2.0", 44 | "typescript": "^4.9.3", 45 | "vite": "^4.0.4", 46 | "vite-plugin-checker": "^0.5.1", 47 | "vue-tsc": "^1.0.24" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/harness.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, run, PIPE 2 | import signal 3 | import re 4 | import json 5 | from typing import Tuple 6 | from holochain_client.api.admin.client import AdminClient 7 | from pathlib import Path 8 | from os import path 9 | 10 | from holochain_client.api.app.client import AppClient 11 | 12 | 13 | class TestHarness: 14 | admin_client: AdminClient 15 | app_client: AppClient 16 | fixture_path: str 17 | 18 | async def __aenter__(self): 19 | (self._sandbox_process, admin_port) = _start_holochain() 20 | self.admin_client = await AdminClient.create(f"ws://localhost:{admin_port}") 21 | 22 | app_interface = await self.admin_client.attach_app_interface() 23 | self.app_client = await AppClient.create(f"ws://localhost:{app_interface.port}") 24 | 25 | fixture_path = ( 26 | Path(__file__).parent / "../fixture/workdir/fixture.happ" 27 | ).resolve() 28 | if not path.exists(fixture_path): 29 | raise Exception( 30 | "Fixture does not exist, please build it before running this test" 31 | ) 32 | self.fixture_path = str(fixture_path) 33 | 34 | fixture_dna_path = ( 35 | Path(__file__).parent / "../fixture/dnas/fixture/workdir/fixture.dna" 36 | ).resolve() 37 | if not path.exists(fixture_dna_path): 38 | raise Exception( 39 | "Fixture DNA does not exist, please build it before running this test" 40 | ) 41 | self.fixture_dna_path = str(fixture_dna_path) 42 | 43 | return self 44 | 45 | async def __aexit__(self, exc_type, exc, tb): 46 | del exc_type, exc, tb 47 | await self.app_client.close() 48 | await self.admin_client.close() 49 | self._sandbox_process.send_signal(signal.SIGINT) 50 | 51 | 52 | def _start_holochain() -> Tuple[Popen, int]: 53 | ps = run(["hc", "sandbox", "clean"]) 54 | if ps.returncode != 0: 55 | raise Exception("Failed to clean sandbox") 56 | 57 | ps = run( 58 | ["hc", "sandbox", "--piped", "create", "--in-process-lair"], 59 | text=True, 60 | input="passphrase\n", 61 | ) 62 | if ps.returncode != 0: 63 | raise Exception("Failed to create sandbox") 64 | 65 | ps = Popen(["hc", "sandbox", "--piped", "run"], stdin=PIPE, stdout=PIPE, text=True) 66 | ps.stdin.write("passphrase\n") 67 | ps.stdin.flush() 68 | 69 | # TODO if the sandbox fails to start or print the expected magic line, this loop won't exit 70 | admin_port = 0 71 | while True: 72 | line = ps.stdout.readline() 73 | match = re.search(r"#!0 (.*)", line) 74 | if match: 75 | info = json.loads(match.group(1)) 76 | admin_port = info["admin_port"] 77 | break 78 | 79 | return (ps, admin_port) 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Project](https://img.shields.io/badge/Project-Holochain-blue.svg?style=flat-square)](http://holochain.org/) 2 | [![Discord](https://img.shields.io/badge/Discord-DEV.HC-blue.svg?style=flat-square)](https://discord.gg/k55DS5dmPH) 3 | [![License: CAL 1.0](https://img.shields.io/badge/License-CAL%201.0-blue.svg)](https://github.com/holochain/cryptographic-autonomy-license) 4 | [![Twitter Follow](https://img.shields.io/twitter/follow/holochain.svg?style=social&label=Follow)](https://twitter.com/holochain) 5 | 6 | # Holochain Client - Python 7 | 8 | > [!WARNING] 9 | > :radioactive: This package is under development, it is not a complete Holochain client and is not fully tested! :radioactive: 10 | 11 | ### Set up a development environment 12 | 13 | The developer environment for this project relies on Holonix, which you can find out more about in the Holochain [getting started guide](https://developer.holochain.org/get-started/). Once you have Nix installed, you can create a new development environment by entering the following command into your shell at the root of this project: 14 | 15 | ```bash 16 | nix develop 17 | ``` 18 | 19 | Then once the Nix shell has spawned, create a virtual environment: 20 | 21 | ```bash 22 | python -m venv .venv 23 | source .venv/bin/activate 24 | ``` 25 | 26 | Then install dependencies using Poetry: 27 | 28 | ```bash 29 | poetry install --no-root 30 | ``` 31 | 32 | Running the tests is a good way to check your development environment! 33 | 34 | ### Run the tests 35 | 36 | The tests rely on a fixture. The fixture is a simple Holochain app that contains a single zome. It must be built before running the tests. You can do this using: 37 | 38 | ```bash 39 | cd fixture 40 | npm install 41 | npm run build:happ 42 | cd .. 43 | ``` 44 | 45 | If that succeeds then the tests will be able to find the built happ and you can move on to running the tests. 46 | 47 | You can run all the tests using: 48 | 49 | ```bash 50 | poetry run pytest 51 | ``` 52 | 53 | To select a single test suite, pass the path to `pytest`. For example: 54 | 55 | ```bash 56 | poetry run tests/api/app/client_test.py 57 | ``` 58 | 59 | To run a single test, pass the path to the test suite and the use the `-k` flag. For example: 60 | 61 | ```bash 62 | poetry run pytest tests/api/app/client_test.py -k test_call_zome 63 | ``` 64 | 65 | > [!TIP] 66 | > By default `pytest` captures output. Use the `-s` flag in combination with `RUST_LOG=info` to debug tests against Holochain. 67 | 68 | ### Keep the code tidy 69 | 70 | Linting and formatting are done by one tool, [Ruff](https://docs.astral.sh/ruff/). Run the linter using: 71 | 72 | ```bash 73 | poetry run ruff check 74 | ``` 75 | 76 | If you want it to automatically fix the problems it finds, then use: 77 | 78 | ```bash 79 | poetry run ruff check --fix 80 | ``` 81 | 82 | Run the formatter using: 83 | 84 | ```bash 85 | poetry run ruff format 86 | ``` 87 | -------------------------------------------------------------------------------- /holochain_client/api/common/signing.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | from typing import Dict, Optional 3 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey 4 | from holochain_client.api.admin.client import AdminClient 5 | from holochain_client.api.admin.types import ( 6 | CapAccessAssigned, 7 | GrantZomeCallCapability, 8 | GrantedFunctions, 9 | ZomeCallCapGrant, 10 | ) 11 | from holochain_client.api.common.types import AgentPubKey, CellId 12 | import os 13 | import base64 14 | 15 | 16 | class SigningKeyWithIdentity: 17 | _signing_key: Ed25519PrivateKey 18 | 19 | """This will be recognised by Holochain as an agent public key but just contains the generated signing key associated with the signing keypair above.""" 20 | identity: AgentPubKey 21 | 22 | def __init__(self, signing_key: Ed25519PrivateKey): 23 | self._signing_key = signing_key 24 | 25 | # The first 3 bytes are the constant AGENT_PREFIX from holochain/holochain and the last 4 bytes are the location bytes which 26 | # are deliberately set to [0, 0, 0, 0] since this agent identity is only used for signing and so its location is not relevant. 27 | self.identity = bytes( 28 | [ 29 | 132, 30 | 32, 31 | 36, 32 | *signing_key.public_key().public_bytes_raw(), 33 | 0, 34 | 0, 35 | 0, 36 | 0, 37 | ] 38 | ) 39 | assert len(self.identity) == 39 40 | 41 | def sign(self, data: bytes) -> bytes: 42 | return self._signing_key.sign(data) 43 | 44 | 45 | @dataclasses.dataclass 46 | class SigningCredentials: 47 | cap_secret: bytes 48 | signing_key: SigningKeyWithIdentity 49 | 50 | 51 | def generate_signing_keypair() -> SigningKeyWithIdentity: 52 | signing_key = Ed25519PrivateKey.generate() 53 | 54 | return SigningKeyWithIdentity(signing_key) 55 | 56 | 57 | CREDS_STORE: Dict[str, SigningCredentials] = {} 58 | 59 | 60 | def _add_to_creds_store(cell_id: CellId, creds: SigningCredentials): 61 | CREDS_STORE[base64.b64encode(bytes([*cell_id[0], *cell_id[1]]))] = creds 62 | 63 | 64 | def get_from_creds_store(cell_id: CellId) -> Optional[SigningCredentials]: 65 | return CREDS_STORE.get(base64.b64encode(bytes([*cell_id[0], *cell_id[1]]))) 66 | 67 | 68 | async def authorize_signing_credentials( 69 | admin_client: AdminClient, 70 | cell_id: CellId, 71 | functions: Optional[GrantedFunctions] = None, 72 | ): 73 | signing_keypair = generate_signing_keypair() 74 | cap_secret = os.urandom(64) 75 | await admin_client.grant_zome_call_capability( 76 | GrantZomeCallCapability( 77 | cell_id=cell_id, 78 | cap_grant=ZomeCallCapGrant( 79 | tag="zome-call-signing-key", 80 | functions=functions if functions else {"All": None}, 81 | access={ 82 | "Assigned": CapAccessAssigned( 83 | secret=cap_secret, assignees=[signing_keypair.identity] 84 | ) 85 | }, 86 | ), 87 | ) 88 | ) 89 | 90 | _add_to_creds_store(cell_id, SigningCredentials(cap_secret, signing_keypair)) 91 | -------------------------------------------------------------------------------- /holochain_client/api/app/client.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import asyncio 3 | import websockets 4 | from holochain_client.api.app.types import CallZome, ZomeCallUnsigned 5 | from holochain_client.api.common.pending_request_pool import PendingRequestPool 6 | from holochain_client.api.common.request import ( 7 | create_wire_message_request, 8 | tag_from_type, 9 | ) 10 | from holochain_client.api.common.signing import get_from_creds_store 11 | import os 12 | from datetime import datetime 13 | from holochain_serialization import ZomeCallUnsignedPy, get_data_to_sign 14 | 15 | 16 | class AppClient: 17 | url: str 18 | defaultTimeout: int 19 | 20 | client: websockets.WebSocketClientProtocol 21 | requestId: int = 0 22 | pendingRequestPool: PendingRequestPool 23 | 24 | def __init__(self, url: str, defaultTimeout: int = 60) -> None: 25 | self.url = url 26 | self.defaultTimeout = defaultTimeout 27 | 28 | @classmethod 29 | async def create(cls, url: str, defaultTimeout: int = 60): 30 | """The recommended way to create an AppClient""" 31 | self = cls(url, defaultTimeout) 32 | await self.connect() 33 | return self 34 | 35 | def connect_sync(self, event_loop): 36 | event_loop.run_until_complete(self.connect()) 37 | 38 | async def connect(self): 39 | self.client = await websockets.connect(self.url) 40 | self.pendingRequestPool = PendingRequestPool(self.client) 41 | return self 42 | 43 | async def call_zome(self, request: ZomeCallUnsigned) -> bytes: 44 | """ 45 | :return: The literal response from the zome call 46 | """ 47 | signing_credentials = get_from_creds_store(request.cell_id) 48 | if signing_credentials is None: 49 | raise Exception( 50 | f"No signing credentials have been authorized for cell_id: {request.cell_id}" 51 | ) 52 | 53 | provenance = signing_credentials.signing_key.identity # Request is actually made on behalf of the siging credentials, not the current agent! 54 | nonce = os.urandom(32) 55 | expires_at = int((datetime.now().timestamp() + 5 * 60) * 1e6) 56 | cap_secret = signing_credentials.cap_secret 57 | 58 | zome_call_py = ZomeCallUnsignedPy( 59 | provenance, 60 | request.cell_id[0], 61 | request.cell_id[1], 62 | request.zome_name, 63 | request.fn_name, 64 | request.payload, 65 | nonce, 66 | expires_at, 67 | cap_secret=cap_secret, 68 | ) 69 | data_to_sign = bytes(get_data_to_sign(zome_call_py)) 70 | signature = signing_credentials.signing_key.sign(data_to_sign) 71 | 72 | request = CallZome( 73 | cell_id=request.cell_id, 74 | zome_name=request.zome_name, 75 | fn_name=request.fn_name, 76 | payload=request.payload, 77 | provenance=provenance, 78 | signature=signature, 79 | nonce=nonce, 80 | expires_at=expires_at, 81 | cap_secret=cap_secret, 82 | ) 83 | 84 | response = await self._exchange(request, tag_from_type(request)) 85 | assert response["type"] == "zome_called", f"response was: {response}" 86 | return response["data"] 87 | 88 | async def close(self): 89 | await self.client.close() 90 | 91 | async def _exchange(self, request: Any, tag: str) -> Any: 92 | requestId = self.requestId 93 | self.requestId += 1 94 | req = create_wire_message_request(request, tag, requestId) 95 | await self.client.send(req) 96 | 97 | completed = asyncio.Event() 98 | self.pendingRequestPool.add(requestId, completed) 99 | await asyncio.wait_for(completed.wait(), self.defaultTimeout) 100 | 101 | return self.pendingRequestPool.take_response(requestId) 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /fixture/target 2 | /fixture/node_modules 3 | /.cargo 4 | 5 | .ruff_cache 6 | 7 | .hc 8 | .hc_* 9 | *.dna 10 | *.happ 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | share/python-wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | *.py,cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | cover/ 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | db.sqlite3 73 | db.sqlite3-journal 74 | 75 | # Flask stuff: 76 | instance/ 77 | .webassets-cache 78 | 79 | # Scrapy stuff: 80 | .scrapy 81 | 82 | # Sphinx documentation 83 | docs/_build/ 84 | 85 | # PyBuilder 86 | .pybuilder/ 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | # For a library or package, you might want to ignore these files since the code is 98 | # intended to run in multiple environments; otherwise, check them in: 99 | # .python-version 100 | 101 | # pipenv 102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 | # install all needed dependencies. 106 | #Pipfile.lock 107 | 108 | # poetry 109 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 110 | # This is especially recommended for binary packages to ensure reproducibility, and is more 111 | # commonly ignored for libraries. 112 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 113 | #poetry.lock 114 | 115 | # pdm 116 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 117 | #pdm.lock 118 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 119 | # in version control. 120 | # https://pdm.fming.dev/#use-with-ide 121 | .pdm.toml 122 | 123 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 124 | __pypackages__/ 125 | 126 | # Celery stuff 127 | celerybeat-schedule 128 | celerybeat.pid 129 | 130 | # SageMath parsed files 131 | *.sage.py 132 | 133 | # Environments 134 | .env 135 | .venv 136 | env/ 137 | venv/ 138 | ENV/ 139 | env.bak/ 140 | venv.bak/ 141 | 142 | # Spyder project settings 143 | .spyderproject 144 | .spyproject 145 | 146 | # Rope project settings 147 | .ropeproject 148 | 149 | # mkdocs documentation 150 | /site 151 | 152 | # mypy 153 | .mypy_cache/ 154 | .dmypy.json 155 | dmypy.json 156 | 157 | # Pyre type checker 158 | .pyre/ 159 | 160 | # pytype static type analyzer 161 | .pytype/ 162 | 163 | # Cython debug symbols 164 | cython_debug/ 165 | 166 | # PyCharm 167 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 168 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 169 | # and can be added to the global gitignore or merged into this file. For a more nuclear 170 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 171 | #.idea/ 172 | 173 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/integrity/fixture/src/fixture.rs: -------------------------------------------------------------------------------- 1 | use hdi::prelude::*; 2 | #[hdk_entry_helper] 3 | #[derive(Clone, PartialEq)] 4 | pub struct Fixture { 5 | pub name: String, 6 | } 7 | pub fn validate_create_fixture( 8 | _action: EntryCreationAction, 9 | _fixture: Fixture, 10 | ) -> ExternResult { 11 | Ok(ValidateCallbackResult::Valid) 12 | } 13 | pub fn validate_update_fixture( 14 | _action: Update, 15 | _fixture: Fixture, 16 | _original_action: EntryCreationAction, 17 | _original_fixture: Fixture, 18 | ) -> ExternResult { 19 | Ok(ValidateCallbackResult::Valid) 20 | } 21 | pub fn validate_delete_fixture( 22 | _action: Delete, 23 | _original_action: EntryCreationAction, 24 | _original_fixture: Fixture, 25 | ) -> ExternResult { 26 | Ok(ValidateCallbackResult::Valid) 27 | } 28 | pub fn validate_create_link_fixture_updates( 29 | _action: CreateLink, 30 | base_address: AnyLinkableHash, 31 | target_address: AnyLinkableHash, 32 | _tag: LinkTag, 33 | ) -> ExternResult { 34 | let action_hash = base_address 35 | .into_action_hash() 36 | .ok_or( 37 | wasm_error!( 38 | WasmErrorInner::Guest(String::from("No action hash associated with link")) 39 | ), 40 | )?; 41 | let record = must_get_valid_record(action_hash)?; 42 | let _fixture: crate::Fixture = record 43 | .entry() 44 | .to_app_option() 45 | .map_err(|e| wasm_error!(e))? 46 | .ok_or( 47 | wasm_error!( 48 | WasmErrorInner::Guest(String::from("Linked action must reference an entry")) 49 | ), 50 | )?; 51 | let action_hash = target_address 52 | .into_action_hash() 53 | .ok_or( 54 | wasm_error!( 55 | WasmErrorInner::Guest(String::from("No action hash associated with link")) 56 | ), 57 | )?; 58 | let record = must_get_valid_record(action_hash)?; 59 | let _fixture: crate::Fixture = record 60 | .entry() 61 | .to_app_option() 62 | .map_err(|e| wasm_error!(e))? 63 | .ok_or( 64 | wasm_error!( 65 | WasmErrorInner::Guest(String::from("Linked action must reference an entry")) 66 | ), 67 | )?; 68 | Ok(ValidateCallbackResult::Valid) 69 | } 70 | pub fn validate_delete_link_fixture_updates( 71 | _action: DeleteLink, 72 | _original_action: CreateLink, 73 | _base: AnyLinkableHash, 74 | _target: AnyLinkableHash, 75 | _tag: LinkTag, 76 | ) -> ExternResult { 77 | Ok( 78 | ValidateCallbackResult::Invalid( 79 | String::from("FixtureUpdates links cannot be deleted"), 80 | ), 81 | ) 82 | } 83 | pub fn validate_create_link_all_fixtures( 84 | _action: CreateLink, 85 | _base_address: AnyLinkableHash, 86 | target_address: AnyLinkableHash, 87 | _tag: LinkTag, 88 | ) -> ExternResult { 89 | // Check the entry type for the given action hash 90 | let action_hash = target_address 91 | .into_action_hash() 92 | .ok_or( 93 | wasm_error!( 94 | WasmErrorInner::Guest(String::from("No action hash associated with link")) 95 | ), 96 | )?; 97 | let record = must_get_valid_record(action_hash)?; 98 | let _fixture: crate::Fixture = record 99 | .entry() 100 | .to_app_option() 101 | .map_err(|e| wasm_error!(e))? 102 | .ok_or( 103 | wasm_error!( 104 | WasmErrorInner::Guest(String::from("Linked action must reference an entry")) 105 | ), 106 | )?; 107 | // TODO: add the appropriate validation rules 108 | Ok(ValidateCallbackResult::Valid) 109 | } 110 | pub fn validate_delete_link_all_fixtures( 111 | _action: DeleteLink, 112 | _original_action: CreateLink, 113 | _base: AnyLinkableHash, 114 | _target: AnyLinkableHash, 115 | _tag: LinkTag, 116 | ) -> ExternResult { 117 | // TODO: add the appropriate validation rules 118 | Ok(ValidateCallbackResult::Valid) 119 | } 120 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/coordinator/fixture/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod all_fixtures; 2 | pub mod fixture; 3 | use hdk::prelude::*; 4 | use fixture_integrity::*; 5 | #[hdk_extern] 6 | pub fn init(_: ()) -> ExternResult { 7 | Ok(InitCallbackResult::Pass) 8 | } 9 | #[derive(Serialize, Deserialize, Debug)] 10 | #[serde(tag = "type")] 11 | pub enum Signal { 12 | LinkCreated { action: SignedActionHashed, link_type: LinkTypes }, 13 | LinkDeleted { 14 | action: SignedActionHashed, 15 | create_link_action: SignedActionHashed, 16 | link_type: LinkTypes, 17 | }, 18 | EntryCreated { action: SignedActionHashed, app_entry: EntryTypes }, 19 | EntryUpdated { 20 | action: SignedActionHashed, 21 | app_entry: EntryTypes, 22 | original_app_entry: EntryTypes, 23 | }, 24 | EntryDeleted { action: SignedActionHashed, original_app_entry: EntryTypes }, 25 | } 26 | #[hdk_extern(infallible)] 27 | pub fn post_commit(committed_actions: Vec) { 28 | for action in committed_actions { 29 | if let Err(err) = signal_action(action) { 30 | error!("Error signaling new action: {:?}", err); 31 | } 32 | } 33 | } 34 | fn signal_action(action: SignedActionHashed) -> ExternResult<()> { 35 | match action.hashed.content.clone() { 36 | Action::CreateLink(create_link) => { 37 | if let Ok(Some(link_type)) = LinkTypes::from_type( 38 | create_link.zome_index, 39 | create_link.link_type, 40 | ) { 41 | emit_signal(Signal::LinkCreated { 42 | action, 43 | link_type, 44 | })?; 45 | } 46 | Ok(()) 47 | } 48 | Action::DeleteLink(delete_link) => { 49 | let record = get( 50 | delete_link.link_add_address.clone(), 51 | GetOptions::default(), 52 | )? 53 | .ok_or( 54 | wasm_error!( 55 | WasmErrorInner::Guest("Failed to fetch CreateLink action" 56 | .to_string()) 57 | ), 58 | )?; 59 | match record.action() { 60 | Action::CreateLink(create_link) => { 61 | if let Ok(Some(link_type)) = LinkTypes::from_type( 62 | create_link.zome_index, 63 | create_link.link_type, 64 | ) { 65 | emit_signal(Signal::LinkDeleted { 66 | action, 67 | link_type, 68 | create_link_action: record.signed_action.clone(), 69 | })?; 70 | } 71 | Ok(()) 72 | } 73 | _ => { 74 | return Err( 75 | wasm_error!( 76 | WasmErrorInner::Guest("Create Link should exist".to_string()) 77 | ), 78 | ); 79 | } 80 | } 81 | } 82 | Action::Create(_create) => { 83 | if let Ok(Some(app_entry)) = get_entry_for_action(&action.hashed.hash) { 84 | emit_signal(Signal::EntryCreated { 85 | action, 86 | app_entry, 87 | })?; 88 | } 89 | Ok(()) 90 | } 91 | Action::Update(update) => { 92 | if let Ok(Some(app_entry)) = get_entry_for_action(&action.hashed.hash) { 93 | if let Ok(Some(original_app_entry)) = get_entry_for_action( 94 | &update.original_action_address, 95 | ) { 96 | emit_signal(Signal::EntryUpdated { 97 | action, 98 | app_entry, 99 | original_app_entry, 100 | })?; 101 | } 102 | } 103 | Ok(()) 104 | } 105 | Action::Delete(delete) => { 106 | if let Ok(Some(original_app_entry)) = get_entry_for_action( 107 | &delete.deletes_address, 108 | ) { 109 | emit_signal(Signal::EntryDeleted { 110 | action, 111 | original_app_entry, 112 | })?; 113 | } 114 | Ok(()) 115 | } 116 | _ => Ok(()), 117 | } 118 | } 119 | fn get_entry_for_action(action_hash: &ActionHash) -> ExternResult> { 120 | let record = match get_details(action_hash.clone(), GetOptions::default())? { 121 | Some(Details::Record(record_details)) => record_details.record, 122 | _ => { 123 | return Ok(None); 124 | } 125 | }; 126 | let entry = match record.entry().as_option() { 127 | Some(entry) => entry, 128 | None => { 129 | return Ok(None); 130 | } 131 | }; 132 | let (zome_index, entry_index) = match record.action().entry_type() { 133 | Some(EntryType::App(AppEntryDef { zome_index, entry_index, .. })) => { 134 | (zome_index, entry_index) 135 | } 136 | _ => { 137 | return Ok(None); 138 | } 139 | }; 140 | Ok( 141 | EntryTypes::deserialize_from_type( 142 | zome_index.clone(), 143 | entry_index.clone(), 144 | entry, 145 | )?, 146 | ) 147 | } 148 | -------------------------------------------------------------------------------- /holochain_client/api/admin/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from enum import Enum 3 | from typing import Any, Dict, List, Optional, Tuple, Union 4 | from holochain_client.api.common.types import ( 5 | AgentPubKey, 6 | CellId, 7 | DnaHash, 8 | MembraneProof, 9 | NetworkSeed, 10 | RoleName, 11 | ) 12 | 13 | 14 | @dataclass 15 | class WireMessageRequest: 16 | id: int 17 | data: List[int] 18 | type: str = "request" 19 | 20 | 21 | @dataclass 22 | class AdminRequest: 23 | type: str 24 | data: Dict 25 | 26 | 27 | @dataclass 28 | class CellProvisioningCreate: 29 | deferred: bool 30 | strategy: str = "Create" 31 | 32 | 33 | @dataclass 34 | class CellProvisioningCloneOnly: 35 | strategy: str = "CloneOnly" 36 | 37 | 38 | @dataclass 39 | class Duration: 40 | secs: int 41 | nanos: int 42 | 43 | 44 | @dataclass 45 | class DnaModifiers: 46 | network_seed: Optional[NetworkSeed] = None 47 | properties: Optional[Dict] = None 48 | origin_time: Optional[int] = None 49 | quantum_time: Optional[Duration] = None 50 | 51 | 52 | @dataclass 53 | class AppRoleDnaManifest: 54 | bundled: str 55 | modifiers: Dict 56 | installed_hash: Optional[str] = None 57 | clone_limit: int = 0 58 | 59 | 60 | @dataclass 61 | class AppRoleManifest: 62 | name: RoleName 63 | dna: AppRoleDnaManifest 64 | provisioning: Optional[ 65 | Union[CellProvisioningCreate, CellProvisioningCloneOnly] 66 | ] = None 67 | 68 | 69 | @dataclass 70 | class AppManifest: 71 | manifest_version: str 72 | name: str 73 | description: Optional[str] 74 | roles: List[AppRoleManifest] 75 | 76 | 77 | @dataclass 78 | class AppBundleSourceBundle: 79 | manifest: AppManifest 80 | resources: Dict[str, bytes] 81 | 82 | 83 | @dataclass 84 | class InstallApp: 85 | agent_key: AgentPubKey 86 | 87 | # Exactly one of these must be set 88 | bundle: Optional[AppBundleSourceBundle] = None 89 | path: Optional[str] = None 90 | 91 | installed_app_id: Optional[str] = None 92 | membrane_proofs: Dict[RoleName, MembraneProof] = field(default_factory=dict) 93 | network_seed: Optional[NetworkSeed] = None 94 | 95 | 96 | @dataclass 97 | class GenerateAgentPubKey: 98 | pass 99 | 100 | 101 | AppStatusFilter = Enum( 102 | "AppStatusFilter", ["Enabled", "Disabled", "Running", "Stopped", "Paused"] 103 | ) 104 | 105 | 106 | @dataclass 107 | class ListApps: 108 | status_filter: Optional[AppStatusFilter] = None 109 | 110 | 111 | @dataclass 112 | class DumpNetworkStats: 113 | pass 114 | 115 | 116 | @dataclass 117 | class CellInfoProvisioned: 118 | cell_id: CellId 119 | dna_modifiers: DnaModifiers 120 | name: str 121 | 122 | 123 | @dataclass 124 | class CellInfoCloned: 125 | cell_id: CellId 126 | clone_id: str 127 | original_dna_hash: DnaHash 128 | dna_modifiers: DnaModifiers 129 | name: str 130 | enabled: bool 131 | 132 | 133 | @dataclass 134 | class CellInfoStem: 135 | original_dna_hash: DnaHash 136 | dna_modifiers: DnaModifiers 137 | name: Optional[str] 138 | 139 | 140 | @dataclass 141 | class AppinfoStatusPaused: 142 | reason: Any # TODO 143 | type: str = "Paused" 144 | 145 | 146 | @dataclass 147 | class AppInfoStatusDisabled: 148 | reason: Any # TODO 149 | type: str = "Disabled" 150 | 151 | 152 | @dataclass 153 | class AppinfoStatusRunning: 154 | type: str = "Running" 155 | 156 | 157 | CellInfoKind = Enum("CellInfoKind", ["provisioned", "cloned", "stem"]) 158 | 159 | 160 | @dataclass 161 | class AppInfo: 162 | installed_app_id: str 163 | cell_info: Dict[ 164 | RoleName, 165 | List[ 166 | Dict[CellInfoKind, Union[CellInfoProvisioned, CellInfoCloned, CellInfoStem]] 167 | ], 168 | ] 169 | status: Union[AppinfoStatusPaused, AppInfoStatusDisabled, AppinfoStatusRunning] 170 | agent_pub_key: AgentPubKey 171 | manifest: AppManifest 172 | 173 | 174 | @dataclass 175 | class CapAccessUnrestricted: 176 | type: str = "Unrestricted" 177 | 178 | 179 | @dataclass 180 | class CapAccessTransferable: 181 | secret: bytes 182 | type: str = "Transferable" 183 | 184 | 185 | @dataclass 186 | class CapAccessAssigned: 187 | secret: bytes 188 | assignees: List[AgentPubKey] 189 | 190 | 191 | GrantedFunctionKind = Enum("GrantedFunctionKind", ["All", "Listed"]) 192 | GrantedFunctions = Dict[GrantedFunctionKind, Union[None, List[str]]] 193 | 194 | CapAccessKind = Enum("CapAccessKind", ["Unrestricted", "Transferable", "Assigned"]) 195 | 196 | 197 | @dataclass 198 | class ZomeCallCapGrant: 199 | tag: str 200 | access: Dict[ 201 | CapAccessAssigned, 202 | Union[CapAccessUnrestricted, CapAccessTransferable, CapAccessAssigned], 203 | ] 204 | functions: GrantedFunctions 205 | 206 | 207 | @dataclass 208 | class GrantZomeCallCapability: 209 | cell_id: CellId 210 | cap_grant: ZomeCallCapGrant 211 | 212 | 213 | @dataclass 214 | class EnableApp: 215 | installed_app_id: str 216 | 217 | 218 | @dataclass 219 | class AppEnabled: 220 | app: AppInfo 221 | errors: List[Tuple[CellId, str]] 222 | 223 | 224 | @dataclass 225 | class AttachAppInterface: 226 | port: Optional[int] = None 227 | 228 | 229 | @dataclass 230 | class AppInterfaceAttached: 231 | port: int 232 | 233 | 234 | @dataclass 235 | class ListAppInterfaces: 236 | pass 237 | 238 | 239 | @dataclass 240 | class InterfaceDriverWebsocket: 241 | port: int 242 | type: str = "websocket" 243 | 244 | 245 | @dataclass 246 | class AddAdminInterface: 247 | driver: Union[InterfaceDriverWebsocket] 248 | 249 | 250 | @dataclass 251 | class RegisterDnaPayloadPath: 252 | path: str 253 | modifiers: Optional[DnaModifiers] = None 254 | 255 | 256 | @dataclass 257 | class RegisterDnaPayloadHash: 258 | hash: DnaHash 259 | modifiers: DnaModifiers 260 | 261 | 262 | RegisterDnaPayload = Union[RegisterDnaPayloadPath, RegisterDnaPayloadHash] 263 | 264 | 265 | @dataclass 266 | class UninstallApp: 267 | installed_app_id: str 268 | 269 | 270 | @dataclass 271 | class ListDnas: 272 | pass 273 | 274 | 275 | @dataclass 276 | class ListCellIds: 277 | pass 278 | 279 | 280 | @dataclass 281 | class DisableApp: 282 | installed_app_id: str 283 | -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/coordinator/fixture/src/fixture.rs: -------------------------------------------------------------------------------- 1 | use hdk::prelude::*; 2 | use fixture_integrity::*; 3 | #[hdk_extern] 4 | pub fn create_fixture(fixture: Fixture) -> ExternResult { 5 | let fixture_hash = create_entry(&EntryTypes::Fixture(fixture.clone()))?; 6 | let record = get(fixture_hash.clone(), GetOptions::default())? 7 | .ok_or( 8 | wasm_error!( 9 | WasmErrorInner::Guest(String::from("Could not find the newly created Fixture")) 10 | ), 11 | )?; 12 | let path = Path::from("all_fixtures"); 13 | create_link( 14 | path.path_entry_hash()?, 15 | fixture_hash.clone(), 16 | LinkTypes::AllFixtures, 17 | (), 18 | )?; 19 | Ok(record) 20 | } 21 | #[hdk_extern] 22 | pub fn get_latest_fixture( 23 | original_fixture_hash: ActionHash, 24 | ) -> ExternResult> { 25 | let links = get_links( 26 | original_fixture_hash.clone(), 27 | LinkTypes::FixtureUpdates, 28 | None, 29 | )?; 30 | let latest_link = links 31 | .into_iter() 32 | .max_by(|link_a, link_b| link_a.timestamp.cmp(&link_b.timestamp)); 33 | let latest_fixture_hash = match latest_link { 34 | Some(link) => { 35 | link.target 36 | .clone() 37 | .into_action_hash() 38 | .ok_or( 39 | wasm_error!( 40 | WasmErrorInner::Guest(String::from("No action hash associated with link")) 41 | ), 42 | )? 43 | } 44 | None => original_fixture_hash.clone(), 45 | }; 46 | get(latest_fixture_hash, GetOptions::default()) 47 | } 48 | #[hdk_extern] 49 | pub fn get_original_fixture( 50 | original_fixture_hash: ActionHash, 51 | ) -> ExternResult> { 52 | let Some(details) = get_details(original_fixture_hash, GetOptions::default())? else { 53 | return Ok(None); 54 | }; 55 | match details { 56 | Details::Record(details) => Ok(Some(details.record)), 57 | _ => { 58 | Err( 59 | wasm_error!( 60 | WasmErrorInner::Guest(String::from("Malformed get details response")) 61 | ), 62 | ) 63 | } 64 | } 65 | } 66 | #[hdk_extern] 67 | pub fn get_all_revisions_for_fixture( 68 | original_fixture_hash: ActionHash, 69 | ) -> ExternResult> { 70 | let Some(original_record) = get_original_fixture(original_fixture_hash.clone())? 71 | else { 72 | return Ok(vec![]); 73 | }; 74 | let links = get_links( 75 | original_fixture_hash.clone(), 76 | LinkTypes::FixtureUpdates, 77 | None, 78 | )?; 79 | let get_input: Vec = links 80 | .into_iter() 81 | .map(|link| Ok( 82 | GetInput::new( 83 | link 84 | .target 85 | .into_action_hash() 86 | .ok_or( 87 | wasm_error!( 88 | WasmErrorInner::Guest(String::from("No action hash associated with link")) 89 | ), 90 | )? 91 | .into(), 92 | GetOptions::default(), 93 | ), 94 | )) 95 | .collect::>>()?; 96 | let records = HDK.with(|hdk| hdk.borrow().get(get_input))?; 97 | let mut records: Vec = records.into_iter().filter_map(|r| r).collect(); 98 | records.insert(0, original_record); 99 | Ok(records) 100 | } 101 | #[derive(Serialize, Deserialize, Debug)] 102 | pub struct UpdateFixtureInput { 103 | pub original_fixture_hash: ActionHash, 104 | pub previous_fixture_hash: ActionHash, 105 | pub updated_fixture: Fixture, 106 | } 107 | #[hdk_extern] 108 | pub fn update_fixture(input: UpdateFixtureInput) -> ExternResult { 109 | let updated_fixture_hash = update_entry( 110 | input.previous_fixture_hash.clone(), 111 | &input.updated_fixture, 112 | )?; 113 | create_link( 114 | input.original_fixture_hash.clone(), 115 | updated_fixture_hash.clone(), 116 | LinkTypes::FixtureUpdates, 117 | (), 118 | )?; 119 | let record = get(updated_fixture_hash.clone(), GetOptions::default())? 120 | .ok_or( 121 | wasm_error!( 122 | WasmErrorInner::Guest(String::from("Could not find the newly updated Fixture")) 123 | ), 124 | )?; 125 | Ok(record) 126 | } 127 | #[hdk_extern] 128 | pub fn delete_fixture(original_fixture_hash: ActionHash) -> ExternResult { 129 | let details = get_details(original_fixture_hash.clone(), GetOptions::default())? 130 | .ok_or( 131 | wasm_error!( 132 | WasmErrorInner::Guest(String::from("{pascal_entry_def_name} not found")) 133 | ), 134 | )?; 135 | match details { 136 | Details::Record(details) => Ok(details.record), 137 | _ => { 138 | Err( 139 | wasm_error!( 140 | WasmErrorInner::Guest(String::from("Malformed get details response")) 141 | ), 142 | ) 143 | } 144 | }?; 145 | let path = Path::from("all_fixtures"); 146 | let links = get_links(path.path_entry_hash()?, LinkTypes::AllFixtures, None)?; 147 | for link in links { 148 | if let Some(hash) = link.target.into_action_hash() { 149 | if hash.eq(&original_fixture_hash) { 150 | delete_link(link.create_link_hash)?; 151 | } 152 | } 153 | } 154 | delete_entry(original_fixture_hash) 155 | } 156 | #[hdk_extern] 157 | pub fn get_all_deletes_for_fixture( 158 | original_fixture_hash: ActionHash, 159 | ) -> ExternResult>> { 160 | let Some(details) = get_details(original_fixture_hash, GetOptions::default())? else { 161 | return Ok(None); 162 | }; 163 | match details { 164 | Details::Entry(_) => { 165 | Err(wasm_error!(WasmErrorInner::Guest("Malformed details".into()))) 166 | } 167 | Details::Record(record_details) => Ok(Some(record_details.deletes)), 168 | } 169 | } 170 | #[hdk_extern] 171 | pub fn get_oldest_delete_for_fixture( 172 | original_fixture_hash: ActionHash, 173 | ) -> ExternResult> { 174 | let Some(mut deletes) = get_all_deletes_for_fixture(original_fixture_hash)? else { 175 | return Ok(None); 176 | }; 177 | deletes 178 | .sort_by(|delete_a, delete_b| { 179 | delete_a.action().timestamp().cmp(&delete_b.action().timestamp()) 180 | }); 181 | Ok(deletes.first().cloned()) 182 | } 183 | -------------------------------------------------------------------------------- /holochain_client/api/admin/client.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | import asyncio 3 | import websockets 4 | from holochain_client.api.admin.types import ( 5 | AddAdminInterface, 6 | AppEnabled, 7 | AppInfo, 8 | AppInterfaceAttached, 9 | AttachAppInterface, 10 | DisableApp, 11 | DumpNetworkStats, 12 | EnableApp, 13 | GenerateAgentPubKey, 14 | GrantZomeCallCapability, 15 | InstallApp, 16 | ListAppInterfaces, 17 | ListApps, 18 | ListCellIds, 19 | ListDnas, 20 | RegisterDnaPayload, 21 | UninstallApp, 22 | ) 23 | import json 24 | from holochain_client.api.common.pending_request_pool import PendingRequestPool 25 | from holochain_client.api.common.request import ( 26 | create_wire_message_request, 27 | tag_from_type, 28 | ) 29 | from holochain_client.api.common.types import AgentPubKey, CellId, DnaHash 30 | import inspect 31 | 32 | 33 | class AdminClient: 34 | url: str 35 | defaultTimeout: int 36 | 37 | client: websockets.WebSocketClientProtocol 38 | requestId: int = 0 39 | pendingRequestPool: PendingRequestPool 40 | 41 | def __init__(self, url: str, defaultTimeout: int = 60): 42 | self.url = url 43 | self.defaultTimeout = defaultTimeout 44 | 45 | @classmethod 46 | async def create(cls, url: str, defaultTimeout: int = 60): 47 | """The recommended way to create an AdminClient""" 48 | self = cls(url, defaultTimeout) 49 | await self.connect() 50 | return self 51 | 52 | def connect_sync(self, event_loop): 53 | return event_loop.run_until_complete(self.connect()) 54 | 55 | async def connect(self): 56 | self.client = await websockets.connect(self.url) 57 | self.pendingRequestPool = PendingRequestPool(self.client) 58 | 59 | async def add_admin_interfaces(self, request: List[AddAdminInterface]): 60 | response = await self._exchange( 61 | request, tag=inspect.currentframe().f_code.co_name 62 | ) 63 | assert response["type"] == "admin_interfaces_added", f"response was: {response}" 64 | 65 | async def register_dna(self, request: RegisterDnaPayload) -> DnaHash: 66 | response = await self._exchange( 67 | request, tag=inspect.currentframe().f_code.co_name 68 | ) 69 | assert response["type"] == "dna_registered", f"response was: {response}" 70 | return response["data"] 71 | 72 | async def get_dna_definition(self, dna_hash: DnaHash) -> object: 73 | response = await self._exchange( 74 | dna_hash, tag=inspect.currentframe().f_code.co_name 75 | ) 76 | assert ( 77 | response["type"] == "dna_definition_returned" 78 | ), f"response was: {response}" 79 | return response["data"] 80 | 81 | async def install_app(self, request: InstallApp) -> AppInfo: 82 | response = await self._exchange(request, tag_from_type(request)) 83 | assert response["type"] == "app_installed", f"response was: {response}" 84 | return AppInfo(**response["data"]) 85 | 86 | async def uninstall_app(self, request: UninstallApp): 87 | response = await self._exchange( 88 | request, tag=inspect.currentframe().f_code.co_name 89 | ) 90 | assert response["type"] == "app_uninstalled", f"response was: {response}" 91 | 92 | async def list_dnas(self, request: ListDnas = ListDnas()) -> List[DnaHash]: 93 | response = await self._exchange(request, tag_from_type(request)) 94 | assert response["type"] == "dnas_listed", f"response was: {response}" 95 | return response["data"] 96 | 97 | async def generate_agent_pub_key( 98 | self, request: GenerateAgentPubKey = GenerateAgentPubKey() 99 | ) -> AgentPubKey: 100 | response = await self._exchange(request, tag_from_type(request)) 101 | assert ( 102 | response["type"] == "agent_pub_key_generated" 103 | ), f"response was: {response}" 104 | return response["data"] 105 | 106 | async def list_cell_ids(self, request: ListCellIds = ListCellIds()) -> List[CellId]: 107 | response = await self._exchange(request, tag_from_type(request)) 108 | assert response["type"] == "cell_ids_listed", f"response was: {response}" 109 | print(response["data"]) 110 | return response["data"] 111 | 112 | async def list_apps(self, request: ListApps = ListApps()) -> List[AppInfo]: 113 | response = await self._exchange(request, tag_from_type(request)) 114 | assert response["type"] == "apps_listed", f"response was: {response}" 115 | return [AppInfo(**x) for x in response["data"]] 116 | 117 | async def enable_app(self, request: EnableApp) -> AppEnabled: 118 | response = await self._exchange(request, tag_from_type(request)) 119 | assert response["type"] == "app_enabled", f"response was: {response}" 120 | return AppEnabled(*response["data"]) 121 | 122 | async def disable_app(self, request: DisableApp): 123 | response = await self._exchange(request, tag_from_type(request)) 124 | assert response["type"] == "app_disabled", f"response was: {response}" 125 | 126 | async def attach_app_interface( 127 | self, request: AttachAppInterface = AttachAppInterface() 128 | ) -> AppInterfaceAttached: 129 | response = await self._exchange(request, tag_from_type(request)) 130 | assert response["type"] == "app_interface_attached", f"response was: {response}" 131 | return AppInterfaceAttached(port=int(response["data"]["port"])) 132 | 133 | async def list_app_interfaces( 134 | self, request: ListAppInterfaces = ListAppInterfaces() 135 | ) -> List[int]: 136 | response = await self._exchange(request, tag_from_type(request)) 137 | assert response["type"] == "app_interfaces_listed", f"response was: {response}" 138 | return response["data"] 139 | 140 | async def dump_network_stats( 141 | self, request: DumpNetworkStats = DumpNetworkStats() 142 | ) -> object: 143 | response = await self._exchange(request, tag_from_type(request)) 144 | assert response["type"] == "network_stats_dumped", f"response was: {response}" 145 | return json.loads(response["data"]) 146 | 147 | async def grant_zome_call_capability(self, request: GrantZomeCallCapability): 148 | response = await self._exchange(request, tag_from_type(request)) 149 | assert ( 150 | response["type"] == "zome_call_capability_granted" 151 | ), f"response was: {response}" 152 | 153 | async def close(self): 154 | await self.client.close() 155 | 156 | async def _exchange(self, request: Any, tag: str) -> Any: 157 | requestId = self.requestId 158 | self.requestId += 1 159 | req = create_wire_message_request(request, tag, requestId) 160 | await self.client.send(req) 161 | 162 | completed = asyncio.Event() 163 | self.pendingRequestPool.add(requestId, completed) 164 | await asyncio.wait_for(completed.wait(), self.defaultTimeout) 165 | 166 | return self.pendingRequestPool.take_response(requestId) 167 | -------------------------------------------------------------------------------- /tests/api/admin/client_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from holochain_client.api.admin.types import ( 3 | AddAdminInterface, 4 | DisableApp, 5 | DnaModifiers, 6 | EnableApp, 7 | InterfaceDriverWebsocket, 8 | InstallApp, 9 | RegisterDnaPayloadHash, 10 | RegisterDnaPayloadPath, 11 | UninstallApp, 12 | ) 13 | from holochain_client.api.admin.client import AdminClient 14 | from tests.harness import TestHarness 15 | 16 | 17 | @pytest.mark.asyncio 18 | async def test_add_admin_interface(): 19 | async with TestHarness() as harness: 20 | # Get a free port 21 | import socket 22 | 23 | sock = socket.socket() 24 | sock.bind(("", 0)) 25 | port = sock.getsockname()[1] 26 | sock.close() 27 | 28 | await harness.admin_client.add_admin_interfaces( 29 | [AddAdminInterface(InterfaceDriverWebsocket(port))] 30 | ) 31 | 32 | new_admin_client = await AdminClient.create(f"ws://localhost:{port}") 33 | 34 | agent_pub_key = await new_admin_client.generate_agent_pub_key() 35 | await new_admin_client.close() 36 | assert len(agent_pub_key) == 39 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_register_dna(): 41 | async with TestHarness() as harness: 42 | dna_hash = await harness.admin_client.register_dna( 43 | RegisterDnaPayloadPath(harness.fixture_dna_path) 44 | ) 45 | assert len(dna_hash) == 39 46 | 47 | dna_hash_alt = await harness.admin_client.register_dna( 48 | RegisterDnaPayloadHash(dna_hash, DnaModifiers(network_seed="testing")) 49 | ) 50 | assert len(dna_hash_alt) == 39 51 | assert dna_hash != dna_hash_alt 52 | 53 | 54 | @pytest.mark.asyncio 55 | async def test_get_dna_definition(): 56 | async with TestHarness() as harness: 57 | dna_hash = await harness.admin_client.register_dna( 58 | RegisterDnaPayloadPath(harness.fixture_dna_path) 59 | ) 60 | assert len(dna_hash) == 39 61 | 62 | dna_def = await harness.admin_client.get_dna_definition(dna_hash) 63 | assert "name" in dna_def 64 | assert dna_def["name"] == "fixture" 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_install_app(): 69 | async with TestHarness() as harness: 70 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 71 | 72 | response = await harness.admin_client.install_app( 73 | InstallApp( 74 | agent_key=agent_pub_key, 75 | installed_app_id="test_app", 76 | path=harness.fixture_path, 77 | ) 78 | ) 79 | 80 | assert response.installed_app_id == "test_app" 81 | 82 | 83 | @pytest.mark.asyncio 84 | async def test_uninstall_app(): 85 | async with TestHarness() as harness: 86 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 87 | 88 | await harness.admin_client.install_app( 89 | InstallApp( 90 | agent_key=agent_pub_key, 91 | installed_app_id="test_app", 92 | path=harness.fixture_path, 93 | ) 94 | ) 95 | 96 | assert len(await harness.admin_client.list_apps()) == 1 97 | 98 | await harness.admin_client.uninstall_app(UninstallApp("test_app")) 99 | 100 | assert len(await harness.admin_client.list_apps()) == 0 101 | 102 | 103 | @pytest.mark.asyncio 104 | async def test_list_dnas(): 105 | async with TestHarness() as harness: 106 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 107 | 108 | await harness.admin_client.install_app( 109 | InstallApp( 110 | agent_key=agent_pub_key, 111 | installed_app_id="test_app", 112 | path=harness.fixture_path, 113 | ) 114 | ) 115 | 116 | assert len(await harness.admin_client.list_dnas()) == 1 117 | 118 | 119 | @pytest.mark.asyncio 120 | async def test_list_cell_ids(): 121 | async with TestHarness() as harness: 122 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 123 | 124 | await harness.admin_client.install_app( 125 | InstallApp( 126 | agent_key=agent_pub_key, 127 | installed_app_id="test_app", 128 | path=harness.fixture_path, 129 | ) 130 | ) 131 | 132 | # Lists running only, so should be empty before enabling 133 | cell_ids = await harness.admin_client.list_cell_ids() 134 | assert len(cell_ids) == 0 135 | 136 | await harness.admin_client.enable_app(EnableApp("test_app")) 137 | 138 | cell_ids = await harness.admin_client.list_cell_ids() 139 | assert len(cell_ids) == 1 140 | assert cell_ids[0][0] == (await harness.admin_client.list_dnas())[0] 141 | assert cell_ids[0][1] == agent_pub_key 142 | 143 | 144 | @pytest.mark.asyncio 145 | async def test_list_apps(): 146 | async with TestHarness() as harness: 147 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 148 | 149 | await harness.admin_client.install_app( 150 | InstallApp( 151 | agent_key=agent_pub_key, 152 | installed_app_id="test_app", 153 | path=harness.fixture_path, 154 | ) 155 | ) 156 | 157 | apps = await harness.admin_client.list_apps() 158 | assert apps[0].agent_pub_key == agent_pub_key 159 | assert apps[0].installed_app_id == "test_app" 160 | 161 | 162 | @pytest.mark.asyncio 163 | async def test_enable_and_disable_app(): 164 | async with TestHarness() as harness: 165 | agent_pub_key = await harness.admin_client.generate_agent_pub_key() 166 | 167 | await harness.admin_client.install_app( 168 | InstallApp( 169 | agent_key=agent_pub_key, 170 | installed_app_id="test_app", 171 | path=harness.fixture_path, 172 | ) 173 | ) 174 | 175 | app_info = (await harness.admin_client.list_apps())[0] 176 | assert "disabled" in app_info.status 177 | 178 | await harness.admin_client.enable_app(EnableApp("test_app")) 179 | 180 | app_info = (await harness.admin_client.list_apps())[0] 181 | assert "running" in app_info.status 182 | 183 | await harness.admin_client.disable_app(DisableApp("test_app")) 184 | 185 | app_info = (await harness.admin_client.list_apps())[0] 186 | assert "disabled" in app_info.status 187 | 188 | 189 | @pytest.mark.asyncio 190 | async def test_attach_app_interface(): 191 | async with TestHarness() as harness: 192 | app_interface = await harness.admin_client.attach_app_interface() 193 | assert app_interface.port > 0 194 | 195 | listed = await harness.admin_client.list_app_interfaces() 196 | assert app_interface.port in listed 197 | 198 | 199 | @pytest.mark.asyncio 200 | async def test_dump_network_stats(): 201 | async with TestHarness() as harness: 202 | network_stats = await harness.admin_client.dump_network_stats() 203 | print("network stats: ", network_stats) 204 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "cargo-chef": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1695999026, 7 | "narHash": "sha256-UtLoZd7YBRSF9uXStfC3geEFqSqZXFh1rLHaP8hre0Y=", 8 | "owner": "LukeMathWalker", 9 | "repo": "cargo-chef", 10 | "rev": "6e96ae5cd023b718ae40d608981e50a6e7d7facf", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "LukeMathWalker", 15 | "ref": "main", 16 | "repo": "cargo-chef", 17 | "type": "github" 18 | } 19 | }, 20 | "cargo-rdme": { 21 | "flake": false, 22 | "locked": { 23 | "lastModified": 1675118998, 24 | "narHash": "sha256-lrYWqu3h88fr8gG3Yo5GbFGYaq5/1Os7UtM+Af0Bg4k=", 25 | "owner": "orium", 26 | "repo": "cargo-rdme", 27 | "rev": "f9dbb6bccc078f4869f45ae270a2890ac9a75877", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "orium", 32 | "ref": "v1.1.0", 33 | "repo": "cargo-rdme", 34 | "type": "github" 35 | } 36 | }, 37 | "crane": { 38 | "inputs": { 39 | "nixpkgs": [ 40 | "holochain", 41 | "nixpkgs" 42 | ] 43 | }, 44 | "locked": { 45 | "lastModified": 1707363936, 46 | "narHash": "sha256-QbqyvGFYt84QNOQLOOTWplZZkzkyDhYrAl/N/9H0vFM=", 47 | "owner": "ipetkov", 48 | "repo": "crane", 49 | "rev": "9107434eda6991e9388ad87b815dafa337446d16", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "ipetkov", 54 | "repo": "crane", 55 | "type": "github" 56 | } 57 | }, 58 | "crate2nix": { 59 | "flake": false, 60 | "locked": { 61 | "lastModified": 1706909251, 62 | "narHash": "sha256-T7G9Uhh77P0kKri/u+Mwa/4YnXwdPsJSwYCiJCCW+fs=", 63 | "owner": "kolloch", 64 | "repo": "crate2nix", 65 | "rev": "15656bb6cb15f55ee3344bf4362e6489feb93db6", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "kolloch", 70 | "repo": "crate2nix", 71 | "type": "github" 72 | } 73 | }, 74 | "empty": { 75 | "flake": false, 76 | "locked": { 77 | "lastModified": 1683792623, 78 | "narHash": "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=", 79 | "owner": "steveej", 80 | "repo": "empty", 81 | "rev": "8e328e450e4cd32e072eba9e99fe92cf2a1ef5cf", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "steveej", 86 | "repo": "empty", 87 | "type": "github" 88 | } 89 | }, 90 | "flake-compat": { 91 | "flake": false, 92 | "locked": { 93 | "lastModified": 1696426674, 94 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 95 | "owner": "edolstra", 96 | "repo": "flake-compat", 97 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 98 | "type": "github" 99 | }, 100 | "original": { 101 | "owner": "edolstra", 102 | "repo": "flake-compat", 103 | "type": "github" 104 | } 105 | }, 106 | "flake-parts": { 107 | "inputs": { 108 | "nixpkgs-lib": "nixpkgs-lib" 109 | }, 110 | "locked": { 111 | "lastModified": 1706830856, 112 | "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=", 113 | "owner": "hercules-ci", 114 | "repo": "flake-parts", 115 | "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f", 116 | "type": "github" 117 | }, 118 | "original": { 119 | "id": "flake-parts", 120 | "type": "indirect" 121 | } 122 | }, 123 | "flake-utils": { 124 | "inputs": { 125 | "systems": "systems" 126 | }, 127 | "locked": { 128 | "lastModified": 1705309234, 129 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 130 | "owner": "numtide", 131 | "repo": "flake-utils", 132 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 133 | "type": "github" 134 | }, 135 | "original": { 136 | "owner": "numtide", 137 | "repo": "flake-utils", 138 | "type": "github" 139 | } 140 | }, 141 | "holochain": { 142 | "inputs": { 143 | "cargo-chef": "cargo-chef", 144 | "cargo-rdme": "cargo-rdme", 145 | "crane": "crane", 146 | "crate2nix": "crate2nix", 147 | "empty": "empty", 148 | "flake-compat": "flake-compat", 149 | "flake-parts": "flake-parts", 150 | "holochain": [ 151 | "holochain", 152 | "empty" 153 | ], 154 | "lair": [ 155 | "holochain", 156 | "empty" 157 | ], 158 | "launcher": [ 159 | "holochain", 160 | "empty" 161 | ], 162 | "nix-filter": "nix-filter", 163 | "nixpkgs": "nixpkgs", 164 | "pre-commit-hooks-nix": "pre-commit-hooks-nix", 165 | "repo-git": "repo-git", 166 | "rust-overlay": "rust-overlay", 167 | "scaffolding": [ 168 | "holochain", 169 | "empty" 170 | ], 171 | "versions": [ 172 | "versions" 173 | ] 174 | }, 175 | "locked": { 176 | "lastModified": 1707719563, 177 | "narHash": "sha256-bECQ40ecjglVRKuYJwyr775ZsFJYh7a6kZN3eozJLMc=", 178 | "owner": "holochain", 179 | "repo": "holochain", 180 | "rev": "76f48f1964100ecd10372f6ad57e4698354bc80f", 181 | "type": "github" 182 | }, 183 | "original": { 184 | "owner": "holochain", 185 | "repo": "holochain", 186 | "type": "github" 187 | } 188 | }, 189 | "holochain_2": { 190 | "flake": false, 191 | "locked": { 192 | "lastModified": 1707245081, 193 | "narHash": "sha256-l9WHMlD9IDuEv/N/3WDCsP3XLUUnZFrOBEZjbWnC+/Y=", 194 | "owner": "holochain", 195 | "repo": "holochain", 196 | "rev": "0a3b2408b2d6482b913b9f0faf58a39b567f763a", 197 | "type": "github" 198 | }, 199 | "original": { 200 | "owner": "holochain", 201 | "ref": "holochain-0.2.6", 202 | "repo": "holochain", 203 | "type": "github" 204 | } 205 | }, 206 | "lair": { 207 | "flake": false, 208 | "locked": { 209 | "lastModified": 1706550351, 210 | "narHash": "sha256-psVjtb+zj0pZnHTj1xNP2pGBd5Ua1cSwdOAdYdUe3yQ=", 211 | "owner": "holochain", 212 | "repo": "lair", 213 | "rev": "b11e65eff11c8ac3bf938607946f5c7201298a65", 214 | "type": "github" 215 | }, 216 | "original": { 217 | "owner": "holochain", 218 | "ref": "lair_keystore-v0.4.2", 219 | "repo": "lair", 220 | "type": "github" 221 | } 222 | }, 223 | "launcher": { 224 | "flake": false, 225 | "locked": { 226 | "lastModified": 1684183666, 227 | "narHash": "sha256-rOE/W/BBYyZKOyypKb8X9Vpc4ty1TNRoI/fV5+01JPw=", 228 | "owner": "holochain", 229 | "repo": "launcher", 230 | "rev": "75ecdd0aa191ed830cc209a984a6030e656042ff", 231 | "type": "github" 232 | }, 233 | "original": { 234 | "owner": "holochain", 235 | "ref": "holochain-0.2", 236 | "repo": "launcher", 237 | "type": "github" 238 | } 239 | }, 240 | "nix-filter": { 241 | "locked": { 242 | "lastModified": 1705332318, 243 | "narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=", 244 | "owner": "numtide", 245 | "repo": "nix-filter", 246 | "rev": "3449dc925982ad46246cfc36469baf66e1b64f17", 247 | "type": "github" 248 | }, 249 | "original": { 250 | "owner": "numtide", 251 | "repo": "nix-filter", 252 | "type": "github" 253 | } 254 | }, 255 | "nixpkgs": { 256 | "locked": { 257 | "lastModified": 1707268954, 258 | "narHash": "sha256-2en1kvde3cJVc3ZnTy8QeD2oKcseLFjYPLKhIGDanQ0=", 259 | "owner": "NixOS", 260 | "repo": "nixpkgs", 261 | "rev": "f8e2ebd66d097614d51a56a755450d4ae1632df1", 262 | "type": "github" 263 | }, 264 | "original": { 265 | "id": "nixpkgs", 266 | "ref": "nixos-unstable", 267 | "type": "indirect" 268 | } 269 | }, 270 | "nixpkgs-lib": { 271 | "locked": { 272 | "dir": "lib", 273 | "lastModified": 1706550542, 274 | "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=", 275 | "owner": "NixOS", 276 | "repo": "nixpkgs", 277 | "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652", 278 | "type": "github" 279 | }, 280 | "original": { 281 | "dir": "lib", 282 | "owner": "NixOS", 283 | "ref": "nixos-unstable", 284 | "repo": "nixpkgs", 285 | "type": "github" 286 | } 287 | }, 288 | "pre-commit-hooks-nix": { 289 | "flake": false, 290 | "locked": { 291 | "lastModified": 1707297608, 292 | "narHash": "sha256-ADjo/5VySGlvtCW3qR+vdFF4xM9kJFlRDqcC9ZGI8EA=", 293 | "owner": "cachix", 294 | "repo": "pre-commit-hooks.nix", 295 | "rev": "0db2e67ee49910adfa13010e7f012149660af7f0", 296 | "type": "github" 297 | }, 298 | "original": { 299 | "owner": "cachix", 300 | "repo": "pre-commit-hooks.nix", 301 | "type": "github" 302 | } 303 | }, 304 | "repo-git": { 305 | "flake": false, 306 | "locked": { 307 | "narHash": "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY=", 308 | "type": "file", 309 | "url": "file:/dev/null" 310 | }, 311 | "original": { 312 | "type": "file", 313 | "url": "file:/dev/null" 314 | } 315 | }, 316 | "root": { 317 | "inputs": { 318 | "holochain": "holochain", 319 | "nixpkgs": [ 320 | "holochain", 321 | "nixpkgs" 322 | ], 323 | "versions": "versions" 324 | } 325 | }, 326 | "rust-overlay": { 327 | "inputs": { 328 | "flake-utils": "flake-utils", 329 | "nixpkgs": [ 330 | "holochain", 331 | "nixpkgs" 332 | ] 333 | }, 334 | "locked": { 335 | "lastModified": 1707703915, 336 | "narHash": "sha256-Vej69igzNr3eVDca6+32uO+TXjVWx6ZUwwy3iZuzhJ4=", 337 | "owner": "oxalica", 338 | "repo": "rust-overlay", 339 | "rev": "e6679d2ff9136d00b3a7168d2bf1dff9e84c5758", 340 | "type": "github" 341 | }, 342 | "original": { 343 | "owner": "oxalica", 344 | "repo": "rust-overlay", 345 | "type": "github" 346 | } 347 | }, 348 | "scaffolding": { 349 | "flake": false, 350 | "locked": { 351 | "lastModified": 1705076186, 352 | "narHash": "sha256-O914XeBuFulky5RqTOvsog+fbO12v0ppW+mIdO6lvKU=", 353 | "owner": "holochain", 354 | "repo": "scaffolding", 355 | "rev": "42e025fe23ac0b4cd659d95e6752d2d300a8d076", 356 | "type": "github" 357 | }, 358 | "original": { 359 | "owner": "holochain", 360 | "ref": "holochain-0.2", 361 | "repo": "scaffolding", 362 | "type": "github" 363 | } 364 | }, 365 | "systems": { 366 | "locked": { 367 | "lastModified": 1681028828, 368 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 369 | "owner": "nix-systems", 370 | "repo": "default", 371 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 372 | "type": "github" 373 | }, 374 | "original": { 375 | "owner": "nix-systems", 376 | "repo": "default", 377 | "type": "github" 378 | } 379 | }, 380 | "versions": { 381 | "inputs": { 382 | "holochain": "holochain_2", 383 | "lair": "lair", 384 | "launcher": "launcher", 385 | "scaffolding": "scaffolding" 386 | }, 387 | "locked": { 388 | "dir": "versions/0_2", 389 | "lastModified": 1707719563, 390 | "narHash": "sha256-bECQ40ecjglVRKuYJwyr775ZsFJYh7a6kZN3eozJLMc=", 391 | "owner": "holochain", 392 | "repo": "holochain", 393 | "rev": "76f48f1964100ecd10372f6ad57e4698354bc80f", 394 | "type": "github" 395 | }, 396 | "original": { 397 | "dir": "versions/0_2", 398 | "owner": "holochain", 399 | "repo": "holochain", 400 | "type": "github" 401 | } 402 | } 403 | }, 404 | "root": "root", 405 | "version": 7 406 | } 407 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Cryptographic Autonomy License version 1.0 2 | 3 | *This Cryptographic Autonomy License (the “License”) applies to any Work whose owner has marked it with any of the following notices:* 4 | 5 | *“Licensed under the Cryptographic Autonomy License version 1.0,” or* 6 | 7 | *“SPDX-License-Identifier: CAL-1.0,” or* 8 | 9 | *“Licensed under the Cryptographic Autonomy License version 1.0, with Combined Work Exception,” or* 10 | 11 | *“SPDX-License-Identifier: CAL-1.0 with Combined-Work-Exception.”* 12 | 13 | ------ 14 | 15 | ## 1. Purpose 16 | 17 | This License gives You unlimited permission to use and modify the software to which it applies (the “Work”), either as-is or in modified form, for Your private purposes, while protecting the owners and contributors to the software from liability. 18 | 19 | This License also strives to protect the freedom and autonomy of third parties who receive the Work from you. If any non-affiliated third party receives any part, aspect, or element of the Work from You, this License requires that You provide that third party all the permissions and materials needed to independently use and modify the Work without that third party having a loss of data or capability due to your actions. 20 | 21 | The full permissions, conditions, and other terms are laid out below. 22 | 23 | ## 2. Receiving a License 24 | 25 | In order to receive this License, You must agree to its rules. The rules of this License are both obligations of Your agreement with the Licensor and conditions to your License. You must not do anything with the Work that triggers a rule You cannot or will not follow. 26 | 27 | ### 2.1. Application 28 | 29 | The terms of this License apply to the Work as you receive it from Licensor, as well as to any modifications, elaborations, or implementations created by You that contain any licenseable portion of the Work (a “Modified Work”). Unless specified, any reference to the Work also applies to a Modified Work. 30 | 31 | ### 2.2. Offer and Acceptance 32 | 33 | This License is automatically offered to every person and organization. You show that you accept this License and agree to its conditions by taking any action with the Work that, absent this License, would infringe any intellectual property right held by Licensor. 34 | 35 | ### 2.3. Compliance and Remedies 36 | 37 | Any failure to act according to the terms and conditions of this License places Your use of the Work outside the scope of the License and infringes the intellectual property rights of the Licensor. In the event of infringement, the terms and conditions of this License may be enforced by Licensor under the intellectual property laws of any jurisdiction to which You are subject. You also agree that either the Licensor or a Recipient (as an intended third-party beneficiary) may enforce the terms and conditions of this License against You via specific performance. 38 | 39 | ## 3. Permissions and Conditions 40 | 41 | ### 3.1. Permissions Granted 42 | 43 | Conditioned on compliance with section 4, and subject to the limitations of section 3.2, Licensor grants You the world-wide, royalty-free, non-exclusive permission to: 44 | 45 | > a) Take any action with the Work that would infringe the non-patent intellectual property laws of any jurisdiction to which You are subject; and 46 | > 47 | > b) Take any action with the Work that would infringe any patent claims that Licensor can license or becomes able to license, to the extent that those claims are embodied in the Work as distributed by Licensor. 48 | 49 | ### 3.2. Limitations on Permissions Granted 50 | 51 | The following limitations apply to the permissions granted in section 3.1: 52 | 53 | > a) Licensor does not grant any patent license for claims that are only infringed due to modification of the Work as provided by Licensor, or the combination of the Work as provided by Licensor, directly or indirectly, with any other component, including other software or hardware. 54 | > 55 | > b) Licensor does not grant any license to the trademarks, service marks, or logos of Licensor, except to the extent necessary to comply with the attribution conditions in section 4.1 of this License. 56 | 57 | ## 4. Conditions 58 | 59 | If You exercise any permission granted by this License, such that the Work, or any part, aspect, or element of the Work, is distributed, communicated, made available, or made perceptible to a non-Affiliate third party (a “Recipient”), either via physical delivery or via a network connection to the Recipient, You must comply with the following conditions: 60 | 61 | ### 4.1. Provide Access to Source Code 62 | 63 | Subject to the exception in section 4.4, You must provide to each Recipient a copy of, or no-charge unrestricted network access to, the Source Code corresponding to the Work. 64 | 65 | The “Source Code” of the Work means the form of the Work preferred for making modifications, including any comments, configuration information, documentation, help materials, installation instructions, cryptographic seeds or keys, and any information reasonably necessary for the Recipient to independently compile and use the Source Code and to have full access to the functionality contained in the Work. 66 | 67 | #### 4.1.1. Providing Network Access to the Source Code 68 | 69 | Network access to the Notices and Source Code may be provided by You or by a third party, such as a public software repository, and must persist during the same period in which You exercise any of the permissions granted to You under this License and for at least one year thereafter. 70 | 71 | #### 4.1.2. Source Code for a Modified Work 72 | 73 | Subject to the exception in section 4.5, You must provide to each Recipient of a Modified Work Access to Source Code corresponding to those portions of the Work remaining in the Modified Work as well as the modifications used by You to create the Modified Work. The Source Code corresponding to the modifications in the Modified Work must be provided to the Recipient either a) under this License, or b) under a Compatible Open Source License. 74 | 75 | A “Compatible Open Source License” means a license accepted by the Open Source Initiative that allows object code created using both Source Code provided under this License and Source Code provided under the other open source license to be distributed together as a single work. 76 | 77 | #### 4.1.3. Coordinated Disclosure of Security Vulnerabilities 78 | 79 | You may delay providing the Source Code corresponding to a particular modification of the Work for up to ninety (90) days (the “Embargo Period”) if: a) the modification is intended to address a newly-identified vulnerability or a security flaw in the Work, b) disclosure of the vulnerability or security flaw before the end of the Embargo Period would put the data, identity, or autonomy of one or more Recipients of the Work at significant risk, c) You are participating in a coordinated disclosure of the vulnerability or security flaw with one or more additional Licensees, and d) Access to the Source Code pertaining to the modification is provided to all Recipients at the end of the Embargo Period. 80 | 81 | ### 4.2. Maintain User Autonomy 82 | 83 | In addition to providing each Recipient the opportunity to have Access to the Source Code, You cannot use the permissions given under this License to interfere with a Recipient’s ability to fully use an independent copy of the Work generated from the Source Code You provide with the Recipient’s own User Data. 84 | 85 | “User Data” means any data that is an input to or an output from the Work, where the presence of the data is necessary for substantially identical use of the Work in an equivalent context chosen by the Recipient, and where the Recipient has an existing ownership interest, an existing right to possess, or where the data has been generated by, for, or has been assigned to the Recipient. 86 | 87 | #### 4.2.1. No Withholding User Data 88 | 89 | Throughout any period in which You exercise any of the permissions granted to You under this License, You must also provide to any Recipient to whom you provide services via the Work, a no-charge copy, provided in a commonly used electronic form, of the Recipient’s User Data in your possession, to the extent that such User Data is available to You for use in conjunction with the Work. 90 | 91 | #### 4.2.2. No Technical Measures that Limit Access 92 | 93 | You may not, by the use of cryptographic methods applied to anything provided to the Recipient, by possession or control of cryptographic keys, seeds, or hashes, by other technological protection measures, or by any other method, limit a Recipient's ability to access any functionality present in the Recipient's independent copy of the Work, or deny a Recipient full control of the Recipient's User Data. 94 | 95 | #### 4.2.3. No Legal or Contractual Measures that Limit Access 96 | 97 | You may not contractually restrict a Recipient's ability to independently exercise the permissions granted under this License. You waive any legal power to forbid circumvention of technical protection measures that include use of the Work, and You waive any claim that the capabilities of the Work were limited or modified as a means of enforcing the legal rights of third parties against Recipients. 98 | 99 | ### 4.3. Provide Notices and Attribution 100 | 101 | You must retain all licensing, authorship, or attribution notices contained in the Source Code (the “Notices”), and provide all such Notices to each Recipient, together with a statement acknowledging the use of the Work. Notices may be provided directly to a Recipient or via an easy-to-find hyperlink to an Internet location also providing Access to Source Code. 102 | 103 | ### 4.4. Scope of Conditions in this License 104 | 105 | You are required to uphold the conditions of this License only relative to those who are Recipients of the Work from You. Other than providing Recipients with the applicable Notices, Access to Source Code, and a copy of and full control of their User Data, nothing in this License requires You to provide processing services to or engage in network interactions with anyone. 106 | 107 | ### 4.5. Combined Work Exception 108 | 109 | As an exception to condition that You provide Recipients Access to Source Code, any Source Code files marked by the Licensor as having the “Combined Work Exception,” or any object code exclusively resulting from Source Code files so marked, may be combined with other Software into a “Larger Work.” So long as you comply with the requirements to provide Recipients the applicable Notices and Access to the Source Code provided to You by Licensor, and you provide Recipients access to their User Data and do not limit Recipient’s ability to independently work with their User Data, any other Software in the Larger Work as well as the Larger Work as a whole may be licensed under the terms of your choice. 110 | 111 | ## 5. Term and Termination 112 | 113 | The term of this License begins when You receive the Work, and continues until terminated for any of the reasons described herein, or until all Licensor’s intellectual property rights in the Software expire, whichever comes first (“Term”). This License cannot be revoked, only terminated for the reasons listed below. 114 | 115 | ### 5.1. Effect of Termination 116 | 117 | If this License is terminated for any reason, all permissions granted to You under Section 3 by any Licensor automatically terminate. You will immediately cease exercising any permissions granted in this License relative to the Work, including as part of any Modified Work. 118 | 119 | ### 5.2. Termination for Non-Compliance; Reinstatement 120 | 121 | This License terminates automatically if You fail to comply with any of the conditions in section 4. As a special exception to termination for non-compliance, Your permissions for the Work under this License will automatically be reinstated if You come into compliance with all the conditions in section 2 within sixty (60) days of being notified by Licensor or an intended third party beneficiary of Your noncompliance. You are eligible for reinstatement of permissions for the Work one time only, and only for the sixty days immediately after becoming aware of noncompliance. Loss of permissions granted for the Work under this License due to either a) sustained noncompliance lasting more than sixty days or b) subsequent termination for noncompliance after reinstatement, is permanent, unless rights are specifically restored by Licensor in writing. 122 | 123 | ### 5.3 Termination Due to Litigation 124 | 125 | If You initiate litigation against Licensor, or any Recipient of the Work, either direct or indirect, asserting that the Work directly or indirectly infringes any patent, then all permissions granted to You by this License shall terminate. In the event of termination due to litigation, all permissions validly granted by You under this License, directly or indirectly, shall survive termination. Administrative review procedures, declaratory judgment actions, counterclaims in response to patent litigation, and enforcement actions against former Licensees terminated under this section do not cause termination due to litigation. 126 | 127 | ## 6. Disclaimer of Warranty and Limit on Liability 128 | 129 | As far as the law allows, the Work comes AS-IS, without any warranty of any kind, and no Licensor or contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim, or for any type of damages, including indirect, special, incidental, or consequential damages of any type arising as a result of this License or the use of the Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss of profits, revenue, or any and all other commercial damages or losses. 130 | 131 | ## 7. Other Provisions 132 | 133 | ### 7.1. Affiliates 134 | 135 | An “Affiliate” means any other entity that, directly or indirectly through one or more intermediaries, controls, is controlled by, or is under common control with, the Licensee. Employees of a Licensee and natural persons acting as contractors exclusively providing services to Licensee are also Affiliates. 136 | 137 | ### 7.2. Choice of Jurisdiction and Governing Law 138 | 139 | A Licensor may require that any action or suit by a Licensee relating to a Work provided by Licensor under this License may be brought only in the courts of a particular jurisdiction and under the laws of a particular jurisdiction (excluding its conflict-of-law provisions), if Licensor provides conspicuous notice of the particular jurisdiction to all Licensees. 140 | 141 | ### 7.3. No Sublicensing 142 | 143 | This License is not sublicensable. Each time You provide the Work or a Modified Work to a Recipient, the Recipient automatically receives a license under the terms described in this License. You may not impose any further reservations, conditions, or other provisions on any Recipients’ exercise of the permissions granted herein. 144 | 145 | ### 7.4. Attorneys' Fees 146 | 147 | In any action to enforce the terms of this License, or seeking damages relating thereto, including by an intended third party beneficiary, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. A “prevailing party” is the party that achieves, or avoids, compliance with this License, including through settlement. This section shall survive the termination of this License. 148 | 149 | ### 7.5. No Waiver 150 | 151 | Any failure by Licensor to enforce any provision of this License will not constitute a present or future waiver of such provision nor limit Licensor’s ability to enforce such provision at a later time. 152 | 153 | ### 7.6. Severability 154 | 155 | If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any invalid or unenforceable portion will be interpreted to the effect and intent of the original portion. If such a construction is not possible, the invalid or unenforceable portion will be severed from this License but the rest of this License will remain in full force and effect. 156 | 157 | ### 7.7. License for the Text of this License 158 | 159 | The text of this license is released under the Creative Commons Attribution-ShareAlike 4.0 International License, with the caveat that any modifications of this license may not use the name “Cryptographic Autonomy License” or any name confusingly similar thereto to describe any derived work of this License. -------------------------------------------------------------------------------- /fixture/dnas/fixture/zomes/integrity/fixture/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod fixture; 2 | pub use fixture::*; 3 | use hdi::prelude::*; 4 | #[derive(Serialize, Deserialize)] 5 | #[serde(tag = "type")] 6 | #[hdk_entry_defs] 7 | #[unit_enum(UnitEntryTypes)] 8 | pub enum EntryTypes { 9 | Fixture(Fixture), 10 | } 11 | #[derive(Serialize, Deserialize)] 12 | #[hdk_link_types] 13 | pub enum LinkTypes { 14 | FixtureUpdates, 15 | AllFixtures, 16 | } 17 | #[hdk_extern] 18 | pub fn genesis_self_check( 19 | _data: GenesisSelfCheckData, 20 | ) -> ExternResult { 21 | Ok(ValidateCallbackResult::Valid) 22 | } 23 | pub fn validate_agent_joining( 24 | _agent_pub_key: AgentPubKey, 25 | _membrane_proof: &Option, 26 | ) -> ExternResult { 27 | Ok(ValidateCallbackResult::Valid) 28 | } 29 | #[hdk_extern] 30 | pub fn validate(op: Op) -> ExternResult { 31 | match op.flattened::()? { 32 | FlatOp::StoreEntry(store_entry) => { 33 | match store_entry { 34 | OpEntry::CreateEntry { app_entry, action } => { 35 | match app_entry { 36 | EntryTypes::Fixture(fixture) => { 37 | validate_create_fixture( 38 | EntryCreationAction::Create(action), 39 | fixture, 40 | ) 41 | } 42 | } 43 | } 44 | OpEntry::UpdateEntry { app_entry, action, .. } => { 45 | match app_entry { 46 | EntryTypes::Fixture(fixture) => { 47 | validate_create_fixture( 48 | EntryCreationAction::Update(action), 49 | fixture, 50 | ) 51 | } 52 | } 53 | } 54 | _ => Ok(ValidateCallbackResult::Valid), 55 | } 56 | } 57 | FlatOp::RegisterUpdate(update_entry) => { 58 | match update_entry { 59 | OpUpdate::Entry { 60 | original_action, 61 | original_app_entry, 62 | app_entry, 63 | action, 64 | } => { 65 | match (app_entry, original_app_entry) { 66 | ( 67 | EntryTypes::Fixture(fixture), 68 | EntryTypes::Fixture(original_fixture), 69 | ) => { 70 | validate_update_fixture( 71 | action, 72 | fixture, 73 | original_action, 74 | original_fixture, 75 | ) 76 | } 77 | } 78 | } 79 | _ => Ok(ValidateCallbackResult::Valid), 80 | } 81 | } 82 | FlatOp::RegisterDelete(delete_entry) => { 83 | match delete_entry { 84 | OpDelete::Entry { original_action, original_app_entry, action } => { 85 | match original_app_entry { 86 | EntryTypes::Fixture(fixture) => { 87 | validate_delete_fixture(action, original_action, fixture) 88 | } 89 | } 90 | } 91 | _ => Ok(ValidateCallbackResult::Valid), 92 | } 93 | } 94 | FlatOp::RegisterCreateLink { 95 | link_type, 96 | base_address, 97 | target_address, 98 | tag, 99 | action, 100 | } => { 101 | match link_type { 102 | LinkTypes::FixtureUpdates => { 103 | validate_create_link_fixture_updates( 104 | action, 105 | base_address, 106 | target_address, 107 | tag, 108 | ) 109 | } 110 | LinkTypes::AllFixtures => { 111 | validate_create_link_all_fixtures( 112 | action, 113 | base_address, 114 | target_address, 115 | tag, 116 | ) 117 | } 118 | } 119 | } 120 | FlatOp::RegisterDeleteLink { 121 | link_type, 122 | base_address, 123 | target_address, 124 | tag, 125 | original_action, 126 | action, 127 | } => { 128 | match link_type { 129 | LinkTypes::FixtureUpdates => { 130 | validate_delete_link_fixture_updates( 131 | action, 132 | original_action, 133 | base_address, 134 | target_address, 135 | tag, 136 | ) 137 | } 138 | LinkTypes::AllFixtures => { 139 | validate_delete_link_all_fixtures( 140 | action, 141 | original_action, 142 | base_address, 143 | target_address, 144 | tag, 145 | ) 146 | } 147 | } 148 | } 149 | FlatOp::StoreRecord(store_record) => { 150 | match store_record { 151 | OpRecord::CreateEntry { app_entry, action } => { 152 | match app_entry { 153 | EntryTypes::Fixture(fixture) => { 154 | validate_create_fixture( 155 | EntryCreationAction::Create(action), 156 | fixture, 157 | ) 158 | } 159 | } 160 | } 161 | OpRecord::UpdateEntry { 162 | original_action_hash, 163 | app_entry, 164 | action, 165 | .. 166 | } => { 167 | let original_record = must_get_valid_record(original_action_hash)?; 168 | let original_action = original_record.action().clone(); 169 | let original_action = match original_action { 170 | Action::Create(create) => EntryCreationAction::Create(create), 171 | Action::Update(update) => EntryCreationAction::Update(update), 172 | _ => { 173 | return Ok( 174 | ValidateCallbackResult::Invalid( 175 | "Original action for an update must be a Create or Update action" 176 | .to_string(), 177 | ), 178 | ); 179 | } 180 | }; 181 | match app_entry { 182 | EntryTypes::Fixture(fixture) => { 183 | let result = validate_create_fixture( 184 | EntryCreationAction::Update(action.clone()), 185 | fixture.clone(), 186 | )?; 187 | if let ValidateCallbackResult::Valid = result { 188 | let original_fixture: Option = original_record 189 | .entry() 190 | .to_app_option() 191 | .map_err(|e| wasm_error!(e))?; 192 | let original_fixture = match original_fixture { 193 | Some(fixture) => fixture, 194 | None => { 195 | return Ok( 196 | ValidateCallbackResult::Invalid( 197 | "The updated entry type must be the same as the original entry type" 198 | .to_string(), 199 | ), 200 | ); 201 | } 202 | }; 203 | validate_update_fixture( 204 | action, 205 | fixture, 206 | original_action, 207 | original_fixture, 208 | ) 209 | } else { 210 | Ok(result) 211 | } 212 | } 213 | } 214 | } 215 | OpRecord::DeleteEntry { original_action_hash, action, .. } => { 216 | let original_record = must_get_valid_record(original_action_hash)?; 217 | let original_action = original_record.action().clone(); 218 | let original_action = match original_action { 219 | Action::Create(create) => EntryCreationAction::Create(create), 220 | Action::Update(update) => EntryCreationAction::Update(update), 221 | _ => { 222 | return Ok( 223 | ValidateCallbackResult::Invalid( 224 | "Original action for a delete must be a Create or Update action" 225 | .to_string(), 226 | ), 227 | ); 228 | } 229 | }; 230 | let app_entry_type = match original_action.entry_type() { 231 | EntryType::App(app_entry_type) => app_entry_type, 232 | _ => { 233 | return Ok(ValidateCallbackResult::Valid); 234 | } 235 | }; 236 | let entry = match original_record.entry().as_option() { 237 | Some(entry) => entry, 238 | None => { 239 | if original_action.entry_type().visibility().is_public() { 240 | return Ok( 241 | ValidateCallbackResult::Invalid( 242 | "Original record for a delete of a public entry must contain an entry" 243 | .to_string(), 244 | ), 245 | ); 246 | } else { 247 | return Ok(ValidateCallbackResult::Valid); 248 | } 249 | } 250 | }; 251 | let original_app_entry = match EntryTypes::deserialize_from_type( 252 | app_entry_type.zome_index.clone(), 253 | app_entry_type.entry_index.clone(), 254 | &entry, 255 | )? { 256 | Some(app_entry) => app_entry, 257 | None => { 258 | return Ok( 259 | ValidateCallbackResult::Invalid( 260 | "Original app entry must be one of the defined entry types for this zome" 261 | .to_string(), 262 | ), 263 | ); 264 | } 265 | }; 266 | match original_app_entry { 267 | EntryTypes::Fixture(original_fixture) => { 268 | validate_delete_fixture( 269 | action, 270 | original_action, 271 | original_fixture, 272 | ) 273 | } 274 | } 275 | } 276 | OpRecord::CreateLink { 277 | base_address, 278 | target_address, 279 | tag, 280 | link_type, 281 | action, 282 | } => { 283 | match link_type { 284 | LinkTypes::FixtureUpdates => { 285 | validate_create_link_fixture_updates( 286 | action, 287 | base_address, 288 | target_address, 289 | tag, 290 | ) 291 | } 292 | LinkTypes::AllFixtures => { 293 | validate_create_link_all_fixtures( 294 | action, 295 | base_address, 296 | target_address, 297 | tag, 298 | ) 299 | } 300 | } 301 | } 302 | OpRecord::DeleteLink { original_action_hash, base_address, action } => { 303 | let record = must_get_valid_record(original_action_hash)?; 304 | let create_link = match record.action() { 305 | Action::CreateLink(create_link) => create_link.clone(), 306 | _ => { 307 | return Ok( 308 | ValidateCallbackResult::Invalid( 309 | "The action that a DeleteLink deletes must be a CreateLink" 310 | .to_string(), 311 | ), 312 | ); 313 | } 314 | }; 315 | let link_type = match LinkTypes::from_type( 316 | create_link.zome_index.clone(), 317 | create_link.link_type.clone(), 318 | )? { 319 | Some(lt) => lt, 320 | None => { 321 | return Ok(ValidateCallbackResult::Valid); 322 | } 323 | }; 324 | match link_type { 325 | LinkTypes::FixtureUpdates => { 326 | validate_delete_link_fixture_updates( 327 | action, 328 | create_link.clone(), 329 | base_address, 330 | create_link.target_address, 331 | create_link.tag, 332 | ) 333 | } 334 | LinkTypes::AllFixtures => { 335 | validate_delete_link_all_fixtures( 336 | action, 337 | create_link.clone(), 338 | base_address, 339 | create_link.target_address, 340 | create_link.tag, 341 | ) 342 | } 343 | } 344 | } 345 | OpRecord::CreatePrivateEntry { .. } => Ok(ValidateCallbackResult::Valid), 346 | OpRecord::UpdatePrivateEntry { .. } => Ok(ValidateCallbackResult::Valid), 347 | OpRecord::CreateCapClaim { .. } => Ok(ValidateCallbackResult::Valid), 348 | OpRecord::CreateCapGrant { .. } => Ok(ValidateCallbackResult::Valid), 349 | OpRecord::UpdateCapClaim { .. } => Ok(ValidateCallbackResult::Valid), 350 | OpRecord::UpdateCapGrant { .. } => Ok(ValidateCallbackResult::Valid), 351 | OpRecord::Dna { .. } => Ok(ValidateCallbackResult::Valid), 352 | OpRecord::OpenChain { .. } => Ok(ValidateCallbackResult::Valid), 353 | OpRecord::CloseChain { .. } => Ok(ValidateCallbackResult::Valid), 354 | OpRecord::InitZomesComplete { .. } => Ok(ValidateCallbackResult::Valid), 355 | _ => Ok(ValidateCallbackResult::Valid), 356 | } 357 | } 358 | FlatOp::RegisterAgentActivity(agent_activity) => { 359 | match agent_activity { 360 | OpActivity::CreateAgent { agent, action } => { 361 | let previous_action = must_get_action(action.prev_action)?; 362 | match previous_action.action() { 363 | Action::AgentValidationPkg( 364 | AgentValidationPkg { membrane_proof, .. }, 365 | ) => validate_agent_joining(agent, membrane_proof), 366 | _ => { 367 | Ok( 368 | ValidateCallbackResult::Invalid( 369 | "The previous action for a `CreateAgent` action must be an `AgentValidationPkg`" 370 | .to_string(), 371 | ), 372 | ) 373 | } 374 | } 375 | } 376 | _ => Ok(ValidateCallbackResult::Valid), 377 | } 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "cffi" 5 | version = "1.16.0" 6 | description = "Foreign Function Interface for Python calling C code." 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 11 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 12 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 13 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 14 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 15 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 16 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 17 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 18 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 19 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 20 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 21 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 22 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 23 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 24 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 25 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 26 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 27 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 28 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 29 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 30 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 31 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 32 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 33 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 34 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 35 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 36 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 37 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 38 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 39 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 40 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 41 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 42 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 43 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 44 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 45 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 46 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 47 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 48 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 49 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 50 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 51 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 52 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 53 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 54 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 55 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 56 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 57 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 58 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 59 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 60 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 61 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 62 | ] 63 | 64 | [package.dependencies] 65 | pycparser = "*" 66 | 67 | [[package]] 68 | name = "colorama" 69 | version = "0.4.6" 70 | description = "Cross-platform colored terminal text." 71 | optional = false 72 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 73 | files = [ 74 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 75 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 76 | ] 77 | 78 | [[package]] 79 | name = "cryptography" 80 | version = "42.0.2" 81 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 82 | optional = false 83 | python-versions = ">=3.7" 84 | files = [ 85 | {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, 86 | {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, 87 | {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, 88 | {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, 89 | {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, 90 | {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, 91 | {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, 92 | {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, 93 | {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, 94 | {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, 95 | {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, 96 | {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, 97 | {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, 98 | {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, 99 | {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, 100 | {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, 101 | {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, 102 | {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, 103 | {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, 104 | {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, 105 | {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, 106 | {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, 107 | {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, 108 | {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, 109 | {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, 110 | {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, 111 | {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, 112 | {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, 113 | {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, 114 | {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, 115 | {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, 116 | {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, 117 | ] 118 | 119 | [package.dependencies] 120 | cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} 121 | 122 | [package.extras] 123 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 124 | docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] 125 | nox = ["nox"] 126 | pep8test = ["check-sdist", "click", "mypy", "ruff"] 127 | sdist = ["build"] 128 | ssh = ["bcrypt (>=3.1.5)"] 129 | test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] 130 | test-randomorder = ["pytest-randomly"] 131 | 132 | [[package]] 133 | name = "exceptiongroup" 134 | version = "1.2.0" 135 | description = "Backport of PEP 654 (exception groups)" 136 | optional = false 137 | python-versions = ">=3.7" 138 | files = [ 139 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 140 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 141 | ] 142 | 143 | [package.extras] 144 | test = ["pytest (>=6)"] 145 | 146 | [[package]] 147 | name = "holochain-serialization" 148 | version = "0.1.0" 149 | description = "" 150 | optional = false 151 | python-versions = ">=3.8" 152 | files = [ 153 | {file = "holochain_serialization-0.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff1c4657262c4ba2824bf25719a3b542625cc6d0e9347fe54ce8edae845a94dc"}, 154 | {file = "holochain_serialization-0.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab655d683cb1faf912856e55cb8ecf8213e0d4bcac7731f7224a4677bf014e42"}, 155 | {file = "holochain_serialization-0.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a41bda8088dbea443e493bf36d6ebd44d74d4652a1c2776157eef4f2501d0aae"}, 156 | {file = "holochain_serialization-0.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f29123eb4c496e587868241086da75ff952f3833ed9b37743f12af9303acb909"}, 157 | {file = "holochain_serialization-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09280dad1425d4b188b07c2069b05a290b16d833bfd1f93449f26726e2f915b7"}, 158 | {file = "holochain_serialization-0.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7051c45d47d034de1d2778b008ab26fb6bd06d341069574d258c8436ee71a853"}, 159 | {file = "holochain_serialization-0.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c95237ed7f81f9c5bc168175844f31c21410601241dc0d69149e36c33fef2169"}, 160 | {file = "holochain_serialization-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55ebb25c0a3486f8f1a74a918c1e0e08e60dd10b029a2fa41377720649194b3c"}, 161 | {file = "holochain_serialization-0.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d02527cd15fbc635f23bb624b3d08bfeaf98c94ab28dceaa8839640f94f94f"}, 162 | {file = "holochain_serialization-0.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1eb3399357dd7013d14ae4af99ff9ae0c373c431116a9aa99383794dcc9aaad9"}, 163 | {file = "holochain_serialization-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a6ef12e94c2088f16c79bf7687ad6a83cd00f232a01a2352a48e48d63da0685"}, 164 | {file = "holochain_serialization-0.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03cc4a56da68c8284d995d5f9b14b04b9e0d2ded85c6190d98e59630cc3a8ab8"}, 165 | {file = "holochain_serialization-0.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:147ceb8c7ba0049bb0e8a484216b00ae2790c13b6db9e3184d1a3d2cdd8db3c2"}, 166 | {file = "holochain_serialization-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ade7c89f870caeb8a72fd023aa5a8e752b2d5ec3aab682da72b57cafee67d479"}, 167 | {file = "holochain_serialization-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92e269aba49cdb59e5caf8e6fbb21daa1afa2db424c60d7d9c16c510dfaf52f"}, 168 | {file = "holochain_serialization-0.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0eea229b56352ce561eabb48173d617ed4d352bf505ad836707cc8fc39efeb94"}, 169 | {file = "holochain_serialization-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cd558c411e98816d44332d48690907ce71d6720c5a347d17cb9d2a8a571cacb"}, 170 | {file = "holochain_serialization-0.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e67658baaf6f23904bfb2b304d996e49dc2c5ebf17b60a3ddd05851b8c68940e"}, 171 | {file = "holochain_serialization-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1109433de6bd7dd512c8ac0b08041e652bbf030b95b219a2e5c35864bf18396c"}, 172 | {file = "holochain_serialization-0.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c8851a1a14a94b71615520f9a147257fee75221d7ad001168803b13a02c530db"}, 173 | {file = "holochain_serialization-0.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7d4cb37a5a7e8f23d3fe9459a6a182b90310387d4265a45da14648e208c8d07"}, 174 | {file = "holochain_serialization-0.1.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23496d8ebfdc73a0af083121e5f55ba7e1808b02f9013d1d4d3ad8f3b002acbd"}, 175 | {file = "holochain_serialization-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce953b9f244f1d7a70bebd28f5a6f03857d4e4a35db601162b9ac159a14fa2be"}, 176 | {file = "holochain_serialization-0.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d986ceb21dc0d961b3bd57067fb07c65ec0de1cbb11b64d486e6a16398d9395"}, 177 | {file = "holochain_serialization-0.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ea03ecf3952d3398313fa9977f0d29f4ecd82bef5da1e133713b4700013947d"}, 178 | {file = "holochain_serialization-0.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2e661521618b542e7f6b824f306799490475555f1de2db61618933d0f366eba"}, 179 | {file = "holochain_serialization-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811a49ca47d1a747a66e91c58505276312d759502442a80d5d8fd5ad00336f99"}, 180 | {file = "holochain_serialization-0.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:93185120b0f11947cb304b8361876cc0f4d5d92973f042014bc2b93c556feea9"}, 181 | {file = "holochain_serialization-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f277d1b5e3b3756632d1e6633d2843b3c7f901f3d01c34bc2956d450d4a4c1e1"}, 182 | {file = "holochain_serialization-0.1.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57c9b8ef1f326d168ff525a4a3de6dfe9494f14940a057b451454f10a8b74f04"}, 183 | {file = "holochain_serialization-0.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2da21ba8cd495966670d33080b786d492b0883f6cb8c4ae6ee80f92ae870d716"}, 184 | {file = "holochain_serialization-0.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d91684fdd9c898a51ab4c0dc086ad2d88f9bfc1e6d496ae7aa02099e43599116"}, 185 | {file = "holochain_serialization-0.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbcd9631ffe6a1c14df05cd5cd9d5cd5b9c9ddcfb9a160579638ba9aed2f185c"}, 186 | {file = "holochain_serialization-0.1.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:601065a9c22595f8be9fe97b185c727bcecd895b4bece7b8c941e488b6b50245"}, 187 | {file = "holochain_serialization-0.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e43cc5a91bc54a1fefe8a952b2e67cf9af6ab955efda8aacd0013f94f05f6b98"}, 188 | {file = "holochain_serialization-0.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f9ccf004d5f5f18cfea77ceb75d602c9f6e4f9566e256bc610f58bac01e5567e"}, 189 | {file = "holochain_serialization-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c32e0318bbefe5efd578b9bff6f593f6f00aea933d10e5a49cdfde97efbfa825"}, 190 | {file = "holochain_serialization-0.1.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38e67a11fa7defaf2839466449718455f880417e70ea5eb2730b2c78afac24ec"}, 191 | {file = "holochain_serialization-0.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25957708756705b4572d63935d06924db74ddc197a8b6dc85f8fa8de912ef429"}, 192 | {file = "holochain_serialization-0.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccdd8d96e828a9e838557090ea2ded3f950963314742887d8b90f835e82a5d4a"}, 193 | {file = "holochain_serialization-0.1.0.tar.gz", hash = "sha256:ea3edf4ee306c1821ae60f86811e79f85278789b6fe7a3307ee257b993f70169"}, 194 | ] 195 | 196 | [[package]] 197 | name = "iniconfig" 198 | version = "2.0.0" 199 | description = "brain-dead simple config-ini parsing" 200 | optional = false 201 | python-versions = ">=3.7" 202 | files = [ 203 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 204 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 205 | ] 206 | 207 | [[package]] 208 | name = "msgpack" 209 | version = "1.0.7" 210 | description = "MessagePack serializer" 211 | optional = false 212 | python-versions = ">=3.8" 213 | files = [ 214 | {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, 215 | {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, 216 | {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, 217 | {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, 218 | {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, 219 | {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, 220 | {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, 221 | {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, 222 | {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, 223 | {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, 224 | {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, 225 | {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, 226 | {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, 227 | {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, 228 | {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, 229 | {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, 230 | {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, 231 | {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, 232 | {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, 233 | {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, 234 | {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, 235 | {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, 236 | {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, 237 | {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, 238 | {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, 239 | {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, 240 | {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, 241 | {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, 242 | {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, 243 | {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, 244 | {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, 245 | {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, 246 | {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, 247 | {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, 248 | {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, 249 | {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, 250 | {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, 251 | {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, 252 | {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, 253 | {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, 254 | {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, 255 | {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, 256 | {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, 257 | {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, 258 | {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, 259 | {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, 260 | {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, 261 | {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, 262 | {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, 263 | {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, 264 | {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, 265 | {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, 266 | {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, 267 | {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, 268 | {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, 269 | {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, 270 | ] 271 | 272 | [[package]] 273 | name = "packaging" 274 | version = "23.2" 275 | description = "Core utilities for Python packages" 276 | optional = false 277 | python-versions = ">=3.7" 278 | files = [ 279 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 280 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 281 | ] 282 | 283 | [[package]] 284 | name = "pluggy" 285 | version = "1.4.0" 286 | description = "plugin and hook calling mechanisms for python" 287 | optional = false 288 | python-versions = ">=3.8" 289 | files = [ 290 | {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, 291 | {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, 292 | ] 293 | 294 | [package.extras] 295 | dev = ["pre-commit", "tox"] 296 | testing = ["pytest", "pytest-benchmark"] 297 | 298 | [[package]] 299 | name = "pycparser" 300 | version = "2.21" 301 | description = "C parser in Python" 302 | optional = false 303 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 304 | files = [ 305 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 306 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 307 | ] 308 | 309 | [[package]] 310 | name = "pytest" 311 | version = "7.4.4" 312 | description = "pytest: simple powerful testing with Python" 313 | optional = false 314 | python-versions = ">=3.7" 315 | files = [ 316 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 317 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 318 | ] 319 | 320 | [package.dependencies] 321 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 322 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 323 | iniconfig = "*" 324 | packaging = "*" 325 | pluggy = ">=0.12,<2.0" 326 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 327 | 328 | [package.extras] 329 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 330 | 331 | [[package]] 332 | name = "pytest-asyncio" 333 | version = "0.23.4" 334 | description = "Pytest support for asyncio" 335 | optional = false 336 | python-versions = ">=3.8" 337 | files = [ 338 | {file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"}, 339 | {file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"}, 340 | ] 341 | 342 | [package.dependencies] 343 | pytest = ">=7.0.0,<8" 344 | 345 | [package.extras] 346 | docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] 347 | testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] 348 | 349 | [[package]] 350 | name = "ruff" 351 | version = "0.2.1" 352 | description = "An extremely fast Python linter and code formatter, written in Rust." 353 | optional = false 354 | python-versions = ">=3.7" 355 | files = [ 356 | {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, 357 | {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, 358 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, 359 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, 360 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, 361 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, 362 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, 363 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, 364 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, 365 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, 366 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, 367 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, 368 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, 369 | {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, 370 | {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, 371 | {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, 372 | {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, 373 | ] 374 | 375 | [[package]] 376 | name = "tomli" 377 | version = "2.0.1" 378 | description = "A lil' TOML parser" 379 | optional = false 380 | python-versions = ">=3.7" 381 | files = [ 382 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 383 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 384 | ] 385 | 386 | [[package]] 387 | name = "websockets" 388 | version = "12.0" 389 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 390 | optional = false 391 | python-versions = ">=3.8" 392 | files = [ 393 | {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, 394 | {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, 395 | {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, 396 | {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, 397 | {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, 398 | {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, 399 | {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, 400 | {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, 401 | {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, 402 | {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, 403 | {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, 404 | {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, 405 | {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, 406 | {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, 407 | {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, 408 | {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, 409 | {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, 410 | {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, 411 | {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, 412 | {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, 413 | {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, 414 | {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, 415 | {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, 416 | {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, 417 | {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, 418 | {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, 419 | {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, 420 | {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, 421 | {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, 422 | {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, 423 | {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, 424 | {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, 425 | {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, 426 | {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, 427 | {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, 428 | {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, 429 | {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, 430 | {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, 431 | {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, 432 | {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, 433 | {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, 434 | {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, 435 | {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, 436 | {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, 437 | {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, 438 | {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, 439 | {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, 440 | {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, 441 | {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, 442 | {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, 443 | {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, 444 | {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, 445 | {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, 446 | {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, 447 | {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, 448 | {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, 449 | {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, 450 | {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, 451 | {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, 452 | {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, 453 | {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, 454 | {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, 455 | {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, 456 | {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, 457 | {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, 458 | {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, 459 | {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, 460 | {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, 461 | {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, 462 | {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, 463 | {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, 464 | {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, 465 | ] 466 | 467 | [metadata] 468 | lock-version = "2.0" 469 | python-versions = "^3.8" 470 | content-hash = "d515c8c47af8495ad8e9056cbcf9bb0737e1ad3d03f6f5c97cba0d7b6badca8e" 471 | --------------------------------------------------------------------------------