From 1fffd5b0c047200ca0f0048034045fc67a4c9b81 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Tue, 19 Mar 2024 21:44:43 +0000 Subject: [PATCH 01/28] save progress --- src/sindri/sindri.py | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index cead396..a4b1225 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -180,7 +180,7 @@ def _print_sindri_logo(self): .***********************+ .***********************-""") - def _hit_api_circuit_create(self, circuit_upload_path: str) -> tuple[int, dict | list]: + def _hit_api_circuit_create(self, circuit_upload_path: str, tags: list[str] | None = None) -> tuple[int, dict | list]: """ Submit a circuit to the `/circuit/create` endpoint. @@ -210,11 +210,12 @@ def _hit_api_circuit_create(self, circuit_upload_path: str) -> tuple[int, dict | return self._hit_api( "POST", "circuit/create", + data={"tags": tags}, files=files, ) def _hit_api_circuit_prove( - self, circuit_id: str, proof_input: str, prover_implementation: str | None = None + self, circuit_id: str, proof_input: str, perform_verify: bool = False, prover_implementation: str | None = None ) -> tuple[int, dict | list]: """ Submit a circuit to the `/circuit//prove` endpoint. @@ -229,7 +230,7 @@ def _hit_api_circuit_prove( f"circuit/{circuit_id}/prove", data={ "proof_input": proof_input, - "perform_verify": self.perform_verify, + "perform_verify": perform_verify, "prover_implementation": prover_implementation, }, ) @@ -286,7 +287,7 @@ def _set_json_request_headers(self) -> None: "Sindri-Client": f"sindri-py-sdk/{self.version} ({platform.platform()}) python_version:{platform.python_version()}", # noqa: E501 } - def create_circuit(self, circuit_upload_path: str, wait: bool = True) -> str: + def create_circuit(self, circuit_upload_path: str, tags: list[str] | None = None, wait: bool = True) -> str: """ Create a circuit and, if `wait=True`, poll the detail endpoint until the circuit has a `status` of `Ready` or `Failed`. @@ -319,7 +320,7 @@ def create_circuit(self, circuit_upload_path: str, wait: bool = True) -> str: if self.verbose_level > 1: print(f" upload_path: {circuit_upload_path}") - response_status_code, response_json = self._hit_api_circuit_create(circuit_upload_path) + response_status_code, response_json = self._hit_api_circuit_create(circuit_upload_path, tags=tags) if response_status_code != 201: raise Sindri.APIError( f"Unable to create a new circuit." @@ -362,7 +363,7 @@ def create_circuit(self, circuit_upload_path: str, wait: bool = True) -> str: ) if self.verbose_level > 0: - self.get_circuit(circuit_id) + self.get_circuit(circuit_id, include_verification_key=True) # Circuit compilation success! return circuit_id @@ -396,7 +397,7 @@ def delete_proof(self, proof_id: str) -> None: f" status={response_status_code} response={response_json}" ) - def get_all_circuit_proofs(self, circuit_id: str) -> list[dict]: + def get_all_circuit_proofs(self, circuit_id: str, include_proof: bool = False, include_public: bool = False, include_smart_contract_calldata: bool = False, include_verification_key: bool = False) -> list[dict]: """Get all proofs for `circuit_id`.""" if self.verbose_level > 0: print(f"Proof: Get all proofs for circuit_id: {circuit_id}") @@ -404,9 +405,10 @@ def get_all_circuit_proofs(self, circuit_id: str) -> list[dict]: "GET", f"circuit/{circuit_id}/proofs", data={ - "include_public": True, - "include_verification_key": True, - "include_proof": True, + "include_proof": include_proof, + "include_public": include_public, + "include_smart_contract_calldata": include_smart_contract_calldata, + "include_verification_key": include_verification_key, }, ) if response_status_code != 200: @@ -427,14 +429,14 @@ def get_all_circuit_proofs(self, circuit_id: str) -> list[dict]: return response_json - def get_all_circuits(self) -> list[dict]: + def get_all_circuits(self, include_verification_key: bool = False) -> list[dict]: """Get all circuits.""" if self.verbose_level > 0: print("Circuit: Get all circuits") response_status_code, response_json = self._hit_api( "GET", "circuit/list", - data={"include_verification_key": True}, + data={"include_verification_key": include_verification_key}, ) if response_status_code != 200: raise Sindri.APIError( @@ -454,7 +456,7 @@ def get_all_circuits(self) -> list[dict]: return response_json - def get_all_proofs(self) -> list[dict]: + def get_all_proofs(self, include_proof: bool = False, include_public: bool = False, include_smart_contract_calldata: bool = False, include_verification_key: bool = False) -> list[dict]: """Get all proofs.""" if self.verbose_level > 0: print("Proof: Get all proofs") @@ -462,9 +464,10 @@ def get_all_proofs(self) -> list[dict]: "GET", "proof/list", data={ - "include_public": True, - "include_verification_key": True, - "include_proof": True, + "include_proof": include_proof, + "include_public": include_public, + "include_smart_contract_calldata": include_smart_contract_calldata, + "include_verification_key": include_verification_key, }, ) if response_status_code != 200: @@ -485,12 +488,12 @@ def get_all_proofs(self) -> list[dict]: return response_json - def get_circuit(self, circuit_id: str) -> dict: + def get_circuit(self, circuit_id: str, include_verification_key: bool = True) -> dict: """Get circuit for `circuit_id`.""" if self.verbose_level > 0: print(f"Circuit: Get circuit detail for circuit_id: {circuit_id}") response_status_code, response_json = self._hit_api_circuit_detail( - circuit_id, include_verification_key=True + circuit_id, include_verification_key=include_verification_key ) if response_status_code != 200: raise Sindri.APIError( @@ -553,7 +556,7 @@ def get_smart_contract_verifier(self, circuit_id: str) -> str: def get_proof( self, proof_id: str, - include_smart_contract_calldata: bool = False, + include_proof: bool = True, include_public: bool = True, include_smart_contract_calldata: bool = False, include_verification_key: bool = False ) -> dict: """ Get proof for `proof_id`. @@ -572,10 +575,10 @@ def get_proof( print(f"Proof: Get proof detail for proof_id: {proof_id}") response_status_code, response_json = self._hit_api_proof_detail( proof_id, - include_proof=True, - include_public=True, + include_proof=include_proof, + include_public=include_public, include_smart_contract_calldata=include_smart_contract_calldata, - include_verification_key=True, + include_verification_key=include_verification_key, ) if response_status_code != 200: raise Sindri.APIError( @@ -616,6 +619,7 @@ def prove_circuit( circuit_id: str, proof_input: str, prover_implementation: str | None = None, + perform_verify: bool = False, wait: bool = True, ) -> str: """ @@ -641,15 +645,11 @@ def prove_circuit( # Return values proof_id = "" - # TODO: HANDLE the JSON/non-JSON - # Convert the proof_input into a json string - # proof_input_json_str = json.dumps(proof_input) - # 1. Submit a proof, obtain a proof_id. if self.verbose_level > 0: print("Prove circuit") response_status_code, response_json = self._hit_api_circuit_prove( - circuit_id, proof_input, prover_implementation=prover_implementation + circuit_id, proof_input, perform_verify=perform_verify, prover_implementation=prover_implementation ) if response_status_code != 201: raise Sindri.APIError( From adb4c9bf3c8372315d7da926362c1ba16f224509 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 20 Mar 2024 14:42:19 +0000 Subject: [PATCH 02/28] add lint checks to CI. progress with code refactoring --- .flake8 | 2 + .github/workflows/lint.yml | 52 +++++++++++ .isort.cfg | 2 + poetry.lock | 185 ++++++++++++++++++++++++++++++++++++- py_format_code.sh | 21 +++++ pyproject.toml | 10 +- src/__init__.py | 0 src/sindri/sindri.py | 174 ++++++++++++++++------------------ test.py | 23 +++++ test2.py | 19 ++++ tests/test_sindri.py | 14 ++- 11 files changed, 399 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100755 py_format_code.sh create mode 100644 src/__init__.py create mode 100644 test.py create mode 100644 test2.py diff --git a/.flake8 b/.flake8 index 918010c..0e2e133 100644 --- a/.flake8 +++ b/.flake8 @@ -6,6 +6,8 @@ exclude = .pytest_cache/* __pycache__/* .vscode + venv + env ignore = # Ignore flake8 errors that conflict with Black diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..544b968 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,52 @@ +name: Lint Codebase + +on: + push: + branches: [ "main" ] + pull_request: + # Do not run on draft pull requests + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + run-lint: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} # Do not run on draft pull requests + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.10.6 + uses: actions/setup-python@v5 + with: + python-version: 3.10.6 + + - name: Install poetry + run: pip install poetry + + - name: Install Dependencies + run: poetry install + + - name: Check formatting with black + if: success() || failure() + run: | + black --check . + + - name: Check import order with isort + if: success() || failure() + run: | + isort --check-only . + + - name: Lint with flake8 + if: success() || failure() + run: | + flake8 . + + - name: Verify typing annotations + if: success() || failure() + run: | + mypy . diff --git a/.isort.cfg b/.isort.cfg index c3b18cd..938b597 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -11,3 +11,5 @@ skip = .pytest_cache/* .vscode __pycache__/* + venv + env diff --git a/poetry.lock b/poetry.lock index 7c0ce8a..fde6cf9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,50 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "bracex" version = "2.4" @@ -160,6 +205,22 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + [[package]] name = "ghp-import" version = "2.1.0" @@ -199,6 +260,23 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.6.1,<4.0" +files = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + [[package]] name = "jinja2" version = "3.1.3" @@ -317,6 +395,17 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -374,6 +463,64 @@ mkdocs = ">=1" natsort = ">=8.1.0" wcmatch = ">=7" +[[package]] +name = "mypy" +version = "1.9.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "natsort" version = "8.4.0" @@ -441,6 +588,28 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + [[package]] name = "pytest" version = "8.1.1" @@ -615,6 +784,20 @@ dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2 doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +[[package]] +name = "types-requests" +version = "2.31.0.20240311" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.31.0.20240311.tar.gz", hash = "sha256:b1c1b66abfb7fa79aae09097a811c4aa97130eb8831c60e47aee4ca344731ca5"}, + {file = "types_requests-2.31.0.20240311-py3-none-any.whl", hash = "sha256:47872893d65a38e282ee9f277a4ee50d1b28bd592040df7d1fdaffdf3779937d"}, +] + +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" version = "4.10.0" @@ -701,4 +884,4 @@ bracex = ">=2.1.1" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "64c57c438370289b563081d92f4f2208c73aa6e85f167cf02db0d2a5f062e8d3" +content-hash = "9b4b7b55dcefc487f8044011c7a5e544c3d9b7bb8a80efe061c9b63c23357a63" diff --git a/py_format_code.sh b/py_format_code.sh new file mode 100755 index 0000000..85335e7 --- /dev/null +++ b/py_format_code.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# File formatters +echo "" +echo "(python) black..." +black . + + +echo "" +echo "(python) isort..." +isort --overwrite-in-place . + +# Analyze code and report errors +echo "" +echo "(python) flake8..." +flake8 . + +echo "" +echo "(python) mypy..." +mypy . + diff --git a/pyproject.toml b/pyproject.toml index 2257adc..0128790 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,12 +10,15 @@ exclude = ''' | .mypy_cache | .pytest_cache | __pycache__ + | venv + | env )/ ) ''' [tool.mypy] python_version = "3.10" +exclude = ["venv/*", 'env/*'] [tool.poetry] authors = [ @@ -32,7 +35,7 @@ license = "MIT" name = "sindri" readme = "README.md" repository = "https://github.com/Sindri-Labs/sindri-python" -version = "0.0.6" +version = "0.0.0" [tool.poetry.dependencies] python = "^3.10" @@ -43,4 +46,9 @@ lazydocs = "^0.4.8" mkdocs = "^1.5.3" mkdocs-awesome-pages-plugin = "^2.9.2" pytest = "^8.1.1" +black = "23.7.0" +flake8 = "6.1.0" +isort = "5.10.1" +mypy = "^1.9.0" +types-requests = "^2.31.0.20240311" diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index a4b1225..0859ab9 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -64,6 +64,8 @@ def __init__( # Obtain version from module self.version = __version__ + self.headers_json: dict = {} # set in set_api_key() + # Do not print anything during initial setup self.set_verbose_level(0) @@ -178,62 +180,25 @@ def _print_sindri_logo(self): :***** .:::::::::::::::::::::++==- .***********************+ - .***********************-""") - - def _hit_api_circuit_create(self, circuit_upload_path: str, tags: list[str] | None = None) -> tuple[int, dict | list]: - """ - Submit a circuit to the `/circuit/create` endpoint. - - Do not poll for the circuit detail. - Return the `response.status_code` and the JSON decoded `response`. - This may raise `Sindri.APIError`. - This does not check if the `response.status_code` is the successful response (201). - - `circuit_upload_path` can be a path to a tar.gz circuit file or the circuit directory. - If it is a directory, it will automatically be tarred before sending. - """ - if not os.path.exists(circuit_upload_path): - raise Sindri.APIError(f"circuit_upload_path does not exist: {circuit_upload_path}") - - if os.path.isfile(circuit_upload_path): - # Assume the path is already a tarfile - files = {"files": open(circuit_upload_path, "rb")} - elif os.path.isdir(circuit_upload_path): - # Create a tar archive and upload via byte stream - circuit_upload_path = os.path.abspath(circuit_upload_path) - file_name = f"{pathlib.Path(circuit_upload_path).stem}.tar.gz" - fh = io.BytesIO() - with tarfile.open(fileobj=fh, mode="w:gz") as tar: - tar.add(circuit_upload_path, arcname=file_name) - files = {"files": fh.getvalue()} # type: ignore - - return self._hit_api( - "POST", - "circuit/create", - data={"tags": tags}, - files=files, + .***********************-""" ) - def _hit_api_circuit_prove( - self, circuit_id: str, proof_input: str, perform_verify: bool = False, prover_implementation: str | None = None - ) -> tuple[int, dict | list]: - """ - Submit a circuit to the `/circuit//prove` endpoint. - - Do not poll for the proof detail. - Return the `response.status_code` and the JSON decoded `response`. - This may raise `Sindri.APIError`. - This does not check if the `response.status_code` is the successful response (201). - """ - return self._hit_api( - "POST", - f"circuit/{circuit_id}/prove", - data={ - "proof_input": proof_input, - "perform_verify": perform_verify, - "prover_implementation": prover_implementation, - }, + def _get_circuit(self, circuit_id: str, include_verification_key: bool = False) -> dict: + """Hit the circuit_detail API endpoint and validate the response. Do not print anything. + This may raise `Sindri.APIError` if the response is invalid.""" + response_status_code, response_json = self._hit_api( + "GET", + f"circuit/{circuit_id}/detail", + data={"include_verification_key": include_verification_key}, ) + if response_status_code != 200: + raise Sindri.APIError( + f"Unable to fetch circuit_id={circuit_id}." + f" status={response_status_code} response={response_json}" + ) + if not isinstance(response_json, dict): + raise Sindri.APIError("Received unexpected type for circuit detail response.") + return response_json def _hit_api_circuit_detail( self, circuit_id: str, include_verification_key: bool = False @@ -287,7 +252,9 @@ def _set_json_request_headers(self) -> None: "Sindri-Client": f"sindri-py-sdk/{self.version} ({platform.platform()}) python_version:{platform.python_version()}", # noqa: E501 } - def create_circuit(self, circuit_upload_path: str, tags: list[str] | None = None, wait: bool = True) -> str: + def create_circuit( + self, circuit_upload_path: str, tags: list[str] | None = None, wait: bool = True + ) -> str: """ Create a circuit and, if `wait=True`, poll the detail endpoint until the circuit has a `status` of `Ready` or `Failed`. @@ -320,7 +287,29 @@ def create_circuit(self, circuit_upload_path: str, tags: list[str] | None = None if self.verbose_level > 1: print(f" upload_path: {circuit_upload_path}") - response_status_code, response_json = self._hit_api_circuit_create(circuit_upload_path, tags=tags) + # Ensure circuit_upload_path exists + if not os.path.exists(circuit_upload_path): + raise Sindri.APIError(f"circuit_upload_path does not exist: {circuit_upload_path}") + # Prepare files for upload + if os.path.isfile(circuit_upload_path): + # Assume the path is already a tarfile + files = {"files": open(circuit_upload_path, "rb")} + elif os.path.isdir(circuit_upload_path): + # Create a tar archive and upload via byte stream + circuit_upload_path = os.path.abspath(circuit_upload_path) + file_name = f"{pathlib.Path(circuit_upload_path).stem}.tar.gz" + fh = io.BytesIO() + with tarfile.open(fileobj=fh, mode="w:gz") as tar: + tar.add(circuit_upload_path, arcname=file_name) + files = {"files": fh.getvalue()} # type: ignore + + # Hit circuit/create API endpoint + response_status_code, response_json = self._hit_api( + "POST", + "circuit/create", + data={"tags": tags}, + files=files, + ) if response_status_code != 201: raise Sindri.APIError( f"Unable to create a new circuit." @@ -328,8 +317,7 @@ def create_circuit(self, circuit_upload_path: str, tags: list[str] | None = None ) if not isinstance(response_json, dict): raise Sindri.APIError("Received unexpected type for circuit detail response.") - - # Obtain circuit_id + # Obtain circuit_id from response circuit_id = response_json.get("circuit_id", "") if self.verbose_level > 0: print(f" circuit_id: {circuit_id}") @@ -339,28 +327,17 @@ def create_circuit(self, circuit_upload_path: str, tags: list[str] | None = None if self.verbose_level > 0: print("Circuit: Poll until Ready/Failed") for _ in range(self.max_polling_iterations): - response_status_code, response_json = self._hit_api_circuit_detail(circuit_id) - if response_status_code != 200: - raise Sindri.APIError( - f"Failure to poll circuit detail." - f" status={response_status_code} response={response_json}" - ) - if not isinstance(response_json, dict): - raise Sindri.APIError("Received unexpected type for circuit detail response.") - circuit_status = response_json.get("status", "") + circuit = self._get_circuit(circuit_id, include_verification_key=False) + circuit_status = circuit.get("status", "") if circuit_status == "Failed": raise Sindri.APIError( - f"Circuit compilation failed." - f" status={response_status_code} response={response_json}" + f"Circuit compilation failed." f" error={circuit.get('error', '')}" ) if circuit_status == "Ready": break time.sleep(self.polling_interval_sec) else: - raise Sindri.APIError( - f"Circuit compile polling timed out." - f" status={response_status_code} response={response_json}" - ) + raise Sindri.APIError("Circuit compile polling timed out.") if self.verbose_level > 0: self.get_circuit(circuit_id, include_verification_key=True) @@ -397,7 +374,14 @@ def delete_proof(self, proof_id: str) -> None: f" status={response_status_code} response={response_json}" ) - def get_all_circuit_proofs(self, circuit_id: str, include_proof: bool = False, include_public: bool = False, include_smart_contract_calldata: bool = False, include_verification_key: bool = False) -> list[dict]: + def get_all_circuit_proofs( + self, + circuit_id: str, + include_proof: bool = False, + include_public: bool = False, + include_smart_contract_calldata: bool = False, + include_verification_key: bool = False, + ) -> list[dict]: """Get all proofs for `circuit_id`.""" if self.verbose_level > 0: print(f"Proof: Get all proofs for circuit_id: {circuit_id}") @@ -456,7 +440,13 @@ def get_all_circuits(self, include_verification_key: bool = False) -> list[dict] return response_json - def get_all_proofs(self, include_proof: bool = False, include_public: bool = False, include_smart_contract_calldata: bool = False, include_verification_key: bool = False) -> list[dict]: + def get_all_proofs( + self, + include_proof: bool = False, + include_public: bool = False, + include_smart_contract_calldata: bool = False, + include_verification_key: bool = False, + ) -> list[dict]: """Get all proofs.""" if self.verbose_level > 0: print("Proof: Get all proofs") @@ -492,24 +482,13 @@ def get_circuit(self, circuit_id: str, include_verification_key: bool = True) -> """Get circuit for `circuit_id`.""" if self.verbose_level > 0: print(f"Circuit: Get circuit detail for circuit_id: {circuit_id}") - response_status_code, response_json = self._hit_api_circuit_detail( - circuit_id, include_verification_key=include_verification_key - ) - if response_status_code != 200: - raise Sindri.APIError( - f"Unable to fetch circuit_id={circuit_id}." - f" status={response_status_code} response={response_json}" - ) - if not isinstance(response_json, dict): - raise Sindri.APIError("Received unexpected type for circuit detail response.") - + circuit = self._get_circuit(circuit_id, include_verification_key=include_verification_key) if self.verbose_level > 0: - circuit_detail = response_json.copy() + circuit_detail = circuit.copy() if self.verbose_level == 1: circuit_detail = self._get_verbose_1_circuit_detail(circuit_detail) print(f"{pformat(circuit_detail, indent=4)}\n") - - return response_json + return circuit def get_smart_contract_verifier(self, circuit_id: str) -> str: """Get the circuit's smart contract verifier code for `circuit_id`. @@ -556,7 +535,10 @@ def get_smart_contract_verifier(self, circuit_id: str) -> str: def get_proof( self, proof_id: str, - include_proof: bool = True, include_public: bool = True, include_smart_contract_calldata: bool = False, include_verification_key: bool = False + include_proof: bool = True, + include_public: bool = True, + include_smart_contract_calldata: bool = False, + include_verification_key: bool = False, ) -> dict: """ Get proof for `proof_id`. @@ -648,8 +630,15 @@ def prove_circuit( # 1. Submit a proof, obtain a proof_id. if self.verbose_level > 0: print("Prove circuit") - response_status_code, response_json = self._hit_api_circuit_prove( - circuit_id, proof_input, perform_verify=perform_verify, prover_implementation=prover_implementation + # Hit the circuit//prove endpoint + response_status_code, response_json = self._hit_api( + "POST", + f"circuit/{circuit_id}/prove", + data={ + "proof_input": proof_input, + "perform_verify": perform_verify, + "prover_implementation": prover_implementation, + }, ) if response_status_code != 201: raise Sindri.APIError( @@ -658,8 +647,7 @@ def prove_circuit( ) if not isinstance(response_json, dict): raise Sindri.APIError("Received unexpected type for proof detail response.") - - # Obtain proof_id + # Obtain proof_id from response proof_id = response_json.get("proof_id", "") if self.verbose_level > 0: print(f" proof_id: {proof_id}") diff --git a/test.py b/test.py new file mode 100644 index 0000000..7429f4c --- /dev/null +++ b/test.py @@ -0,0 +1,23 @@ +import sys + + +class A: + def __init__(self): + pass + + def foo(self): + # Do not print if the caller was A.bar() + print(sys._getframe().f_back.f_code.co_name) + print("foo()") + + def bar(self): + print("bar()") + + self.foo() + + +# print sys._getframe().f_back.f_code.co_name + +a = A() +a.foo() +a.bar() diff --git a/test2.py b/test2.py new file mode 100644 index 0000000..788506c --- /dev/null +++ b/test2.py @@ -0,0 +1,19 @@ +import inspect +from types import FrameType +from typing import cast + + +def demo_the_caller_name() -> str: + """Return the calling function's name.""" + # Ref: https://stackoverflow.com/a/57712700/ + caller_name = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name + print(caller_name) + return caller_name + + +if __name__ == "__main__": + + def _test_caller_name() -> None: + assert demo_the_caller_name() == "_test_caller_name" + + _test_caller_name() diff --git a/tests/test_sindri.py b/tests/test_sindri.py index a98bbcc..6c8522b 100644 --- a/tests/test_sindri.py +++ b/tests/test_sindri.py @@ -3,7 +3,6 @@ from src.sindri.sindri import Sindri - # Load sample data. # This assumes the sindri-resources repo is cloned in the parent dir of this repo dir_name = os.path.dirname(os.path.abspath(__file__)) @@ -21,7 +20,9 @@ # This Circom circuit is slightly slower to compile+prove, but it supports smart contract verifier # code generation and returning the proof formatted as calldata for that smart contract -circom_circuit_dir = os.path.join(sindri_resources_path, "circuit_database", "circom", "multiplier2") +circom_circuit_dir = os.path.join( + sindri_resources_path, "circuit_database", "circom", "multiplier2" +) circom_proof_input_file_path = os.path.join(circom_circuit_dir, "input.json") assert os.path.exists(circom_circuit_dir) assert os.path.exists(circom_proof_input_file_path) @@ -38,7 +39,7 @@ class TestSindriSdk: """Test Sindri SDK methods. - + All methods may raise Sindri.APIError. - If this error is raised, the pytest will fail as well. - Tests that do not assert anything are still successful if they pass because the Sindri SDK @@ -49,7 +50,7 @@ class TestSindriSdk: the one in `test_circuit_create_prove_other()` to test the functionality of the Sindri SDK methods. """ - + def test_user_team_details(self): sindri.get_user_team_details() @@ -81,7 +82,6 @@ def test_circuit_create_prove_other(self): sindri.delete_proof(proof_id) sindri.delete_circuit(circuit_id) - def test_circuit_create_prove_no_wait(self): """ This test is similar to `test_circuit_create_prove_other()`, but it supplies @@ -104,7 +104,6 @@ def test_circuit_create_prove_no_wait(self): raise RuntimeError("Max polling reached") assert status == "Ready" - proof_id = sindri.prove_circuit(circuit_id, noir_proof_input) # manually poll proof detail until status is ready/failed @@ -117,11 +116,10 @@ def test_circuit_create_prove_no_wait(self): else: raise RuntimeError("Max polling reached") assert status == "Ready" - + sindri.delete_proof(proof_id) sindri.delete_circuit(circuit_id) - def test_circuit_create_prove_smart_contract_calldata(self): """ Most SDK methods require a circuit and a proof to be created. From da25660250195fc7e781f11ea40514a82c85c6b6 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 20 Mar 2024 15:24:36 +0000 Subject: [PATCH 03/28] finish refactor. still need to update docstrings --- src/sindri/sindri.py | 143 ++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 89 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 0859ab9..9b935de 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -82,6 +82,52 @@ def __init__( print(f"Sindri API Url: {self.api_url}") print(f"Sindri API Key: {self.api_key}\n") + def _get_circuit(self, circuit_id: str, include_verification_key: bool = False) -> dict: + """Hit the circuit_detail API endpoint and validate the response. Do not print anything. + This may raise `Sindri.APIError` if the response is invalid.""" + response_status_code, response_json = self._hit_api( + "GET", + f"circuit/{circuit_id}/detail", + data={"include_verification_key": include_verification_key}, + ) + if response_status_code != 200: + raise Sindri.APIError( + f"Unable to fetch circuit_id={circuit_id}." + f" status={response_status_code} response={response_json}" + ) + if not isinstance(response_json, dict): + raise Sindri.APIError("Received unexpected type for circuit detail response.") + return response_json + + def _get_proof( + self, + proof_id: str, + include_proof: bool = False, + include_public: bool = False, + include_smart_contract_calldata: bool = False, + include_verification_key: bool = False, + ) -> dict: + """Hit the proof_detail API endpoint and validate the response. Do not print anything. + This may raise `Sindri.APIError` if the response is invalid.""" + response_status_code, response_json = self._hit_api( + "GET", + f"proof/{proof_id}/detail", + data={ + "include_proof": include_proof, + "include_public": include_public, + "include_smart_contract_calldata": include_smart_contract_calldata, + "include_verification_key": include_verification_key, + }, + ) + if response_status_code != 200: + raise Sindri.APIError( + f"Unable to fetch proof_id={proof_id}." + f" status={response_status_code} response={response_json}" + ) + if not isinstance(response_json, dict): + raise Sindri.APIError("Received unexpected type for proof detail response.") + return response_json + def _get_verbose_1_circuit_detail(self, circuit_detail: dict) -> dict: """Return a slim circuit detail object for printing.""" return { @@ -183,65 +229,6 @@ def _print_sindri_logo(self): .***********************-""" ) - def _get_circuit(self, circuit_id: str, include_verification_key: bool = False) -> dict: - """Hit the circuit_detail API endpoint and validate the response. Do not print anything. - This may raise `Sindri.APIError` if the response is invalid.""" - response_status_code, response_json = self._hit_api( - "GET", - f"circuit/{circuit_id}/detail", - data={"include_verification_key": include_verification_key}, - ) - if response_status_code != 200: - raise Sindri.APIError( - f"Unable to fetch circuit_id={circuit_id}." - f" status={response_status_code} response={response_json}" - ) - if not isinstance(response_json, dict): - raise Sindri.APIError("Received unexpected type for circuit detail response.") - return response_json - - def _hit_api_circuit_detail( - self, circuit_id: str, include_verification_key: bool = False - ) -> tuple[int, dict | list]: - """ - Obtain circuit detail by hitting the `/circuit//detail` endpoint. - - Return the `response.status_code` and the JSON decoded `response`. - This may raise `Sindri.APIError`. - This does not check if the `response.status_code` is the successful response (200). - """ - return self._hit_api( - "GET", - f"circuit/{circuit_id}/detail", - data={"include_verification_key": include_verification_key}, - ) - - def _hit_api_proof_detail( - self, - proof_id: str, - include_proof: bool = False, - include_public: bool = False, - include_smart_contract_calldata: bool = False, - include_verification_key: bool = False, - ) -> tuple[int, dict | list]: - """ - Obtain proof detail by hitting the `/proof//detail` endpoint. - - Return the `response.status_code` and the JSON decoded `response`. - This may raise `Sindri.APIError`. - This does not check if the `response.status_code` is the successful response (200). - """ - return self._hit_api( - "GET", - f"proof/{proof_id}/detail", - data={ - "include_proof": include_proof, - "include_public": include_public, - "include_smart_contract_calldata": include_smart_contract_calldata, - "include_verification_key": include_verification_key, - }, - ) - def _set_json_request_headers(self) -> None: """Set JSON request headers (set `self.headers_json`). Use `self.api_key` for `Bearer`. Additionally set the `Sindri-Client` header. @@ -555,28 +542,19 @@ def get_proof( """ if self.verbose_level > 0: print(f"Proof: Get proof detail for proof_id: {proof_id}") - response_status_code, response_json = self._hit_api_proof_detail( + proof = self._get_proof( proof_id, include_proof=include_proof, include_public=include_public, include_smart_contract_calldata=include_smart_contract_calldata, include_verification_key=include_verification_key, ) - if response_status_code != 200: - raise Sindri.APIError( - f"Unable to fetch proof_id={proof_id}." - f" status={response_status_code} response={response_json}" - ) - if not isinstance(response_json, dict): - raise Sindri.APIError("Received unexpected type for proof detail response.") - if self.verbose_level > 0: - proof_detail = response_json.copy() + proof_detail = proof.copy() if self.verbose_level == 1: proof_detail = self._get_verbose_1_proof_detail(proof_detail) print(f"{pformat(proof_detail, indent=4)}\n") - - return response_json + return proof def get_user_team_details(self) -> dict: """Get details about the user or team associated with the provided API Key.""" @@ -657,34 +635,21 @@ def prove_circuit( if self.verbose_level > 0: print("Proof: Poll until Ready/Failed") for _ in range(self.max_polling_iterations): - response_status_code, response_json = self._hit_api_proof_detail( + proof = self._get_proof( proof_id, include_proof=False, include_public=False, + include_smart_contract_calldata=False, include_verification_key=False, ) - if response_status_code != 200: - raise Sindri.APIError( - f"Failure to poll proof detail." - f" status={response_status_code} response={response_json}" - ) - if not isinstance(response_json, dict): - raise Sindri.APIError("Received unexpected type for proof detail response.") - - proof_status = response_json.get("status", "") + proof_status = proof.get("status", "") if proof_status == "Failed": - raise Sindri.APIError( - f"Prove circuit failed." - f" status={response_status_code} response={response_json}" - ) + raise Sindri.APIError(f"Prove circuit failed. error={proof.get('error', '')}") if proof_status == "Ready": break time.sleep(self.polling_interval_sec) else: - raise Sindri.APIError( - f"Prove circuit polling timed out." - f" status={response_status_code} response={response_json}" - ) + raise Sindri.APIError("Prove circuit polling timed out.") if self.verbose_level > 0: self.get_proof(proof_id) From c9d57f1c5806a8732b8554f2fb14601b35701ae8 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 20 Mar 2024 16:19:13 +0000 Subject: [PATCH 04/28] fix ci bug --- .github/workflows/lint.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 544b968..ac5e54f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,19 +34,19 @@ jobs: - name: Check formatting with black if: success() || failure() run: | - black --check . + poetry run black --check . - name: Check import order with isort if: success() || failure() run: | - isort --check-only . + poetry run isort --check-only . - name: Lint with flake8 if: success() || failure() run: | - flake8 . + poetry run flake8 . - name: Verify typing annotations if: success() || failure() run: | - mypy . + poetry run mypy . From a019d9a925060560dc6dabbd6c084e3257c3c5e5 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 20 Mar 2024 16:47:26 +0000 Subject: [PATCH 05/28] begin making proper docstrings for methods --- src/sindri/sindri.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 9b935de..1f7add7 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -13,8 +13,7 @@ class Sindri: - """ - # Sindri API SDK + """# Sindri API SDK A utility class for interacting with the [Sindri API](https://www.sindri.app). @@ -242,27 +241,24 @@ def _set_json_request_headers(self) -> None: def create_circuit( self, circuit_upload_path: str, tags: list[str] | None = None, wait: bool = True ) -> str: - """ - Create a circuit and, if `wait=True`, poll the detail endpoint until the circuit - has a `status` of `Ready` or `Failed`. + """Create a circuit. - Parameters: - - - `circuit_upload_path: str` can be a path to a `.tar.gz` or `.zip` circuit file - or the circuit directory. If it is a directory, it will automatically be tarred - before sending. - - `wait: bool=True`. If `True`, poll the circuit detail until the circuit has a status - of `Ready` or `Failed`. If `False`, submit the circuit and return immediately. + Args: + `circuit_upload_path`: The path to a directory containing your circuit files or + the path to a compressed file (`.tar.gz` or `.zip`) of your circuit directory. + If it is a directory, it will automatically be tarred before sending. + `tags`: A list of tags to assign the circuit. Defaults to `[]"latest"]` if not + sepecified. + `wait`: If `True`, block until the circuit is finished compiling. If `False`, this + will submit the circuit and return immediately. Returns: + `circuit_id`: The unique idetifier for your circuit. This is generated by Sindri. - - `circuit_id: str` - - Raises `Sindri.APIError` if - - - Invalid API Key - - Unable to connect to the API - - If `wait=True`, failed circuit compilation (`status` of `Failed`) + Raises: + `Sindri.APIError`: If your API Key is invalid, there is an error connecting to the + Sindri API, there is an error with your circuit upload, or the + circuit has a compilation error (if `wait=True`). """ # Return value From 6b2a354b9809efb167738ced155c1d1ab2aa17a2 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 20 Mar 2024 16:48:45 +0000 Subject: [PATCH 06/28] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9a31f13..abce610 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Contains SDK code for Sindri APIs. Please see [sindri.app](https://sindri.app) f ## Documentation and Usage Documentation for this repo is automatically generated from the Python docstrings using [lazydocs](https://pypi.org/project/lazydocs/) and published to [sindri.app/docs/reference/sdk/](https://sindri.app/docs/reference/sdk/python) along with the rest of the documentation for Sindri. +Docstrings for functions and methods follow the [google standard](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods) for formatting. ## License [![](https://img.shields.io/github/license/sindri-labs/sindri-python?style=for-the-badge)](https://img.shields.io/github/license/sindri-labs/sindri-python?style=for-the-badge) From 0351a25cc3225287d47dfa3dc8b91a528c87d3a8 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Thu, 21 Mar 2024 22:43:35 +0000 Subject: [PATCH 07/28] remove optional query params from list endpoints to match api change --- src/sindri/sindri.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 1f7add7..e9124dd 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -357,26 +357,13 @@ def delete_proof(self, proof_id: str) -> None: f" status={response_status_code} response={response_json}" ) - def get_all_circuit_proofs( - self, - circuit_id: str, - include_proof: bool = False, - include_public: bool = False, - include_smart_contract_calldata: bool = False, - include_verification_key: bool = False, - ) -> list[dict]: + def get_all_circuit_proofs(self, circuit_id: str) -> list[dict]: """Get all proofs for `circuit_id`.""" if self.verbose_level > 0: print(f"Proof: Get all proofs for circuit_id: {circuit_id}") response_status_code, response_json = self._hit_api( "GET", f"circuit/{circuit_id}/proofs", - data={ - "include_proof": include_proof, - "include_public": include_public, - "include_smart_contract_calldata": include_smart_contract_calldata, - "include_verification_key": include_verification_key, - }, ) if response_status_code != 200: raise Sindri.APIError( @@ -396,14 +383,13 @@ def get_all_circuit_proofs( return response_json - def get_all_circuits(self, include_verification_key: bool = False) -> list[dict]: + def get_all_circuits(self) -> list[dict]: """Get all circuits.""" if self.verbose_level > 0: print("Circuit: Get all circuits") response_status_code, response_json = self._hit_api( "GET", "circuit/list", - data={"include_verification_key": include_verification_key}, ) if response_status_code != 200: raise Sindri.APIError( @@ -423,25 +409,13 @@ def get_all_circuits(self, include_verification_key: bool = False) -> list[dict] return response_json - def get_all_proofs( - self, - include_proof: bool = False, - include_public: bool = False, - include_smart_contract_calldata: bool = False, - include_verification_key: bool = False, - ) -> list[dict]: + def get_all_proofs(self) -> list[dict]: """Get all proofs.""" if self.verbose_level > 0: print("Proof: Get all proofs") response_status_code, response_json = self._hit_api( "GET", "proof/list", - data={ - "include_proof": include_proof, - "include_public": include_public, - "include_smart_contract_calldata": include_smart_contract_calldata, - "include_verification_key": include_verification_key, - }, ) if response_status_code != 200: raise Sindri.APIError( From 70f41957b8032fb8386e0b0dd7f42abec378e410 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Mon, 25 Mar 2024 20:04:24 +0000 Subject: [PATCH 08/28] remove deprecated endpoint. get_all_proofs --- src/sindri/sindri.py | 26 -------------------------- tests/test_sindri.py | 3 --- 2 files changed, 29 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index e9124dd..b572825 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -409,32 +409,6 @@ def get_all_circuits(self) -> list[dict]: return response_json - def get_all_proofs(self) -> list[dict]: - """Get all proofs.""" - if self.verbose_level > 0: - print("Proof: Get all proofs") - response_status_code, response_json = self._hit_api( - "GET", - "proof/list", - ) - if response_status_code != 200: - raise Sindri.APIError( - f"Unable to fetch proofs." - f" status={response_status_code} response={response_json}" - ) - if not isinstance(response_json, list): - raise Sindri.APIError("Received unexpected type for proof list response.") - - if self.verbose_level > 0: - proof_detail_list = response_json.copy() - if self.verbose_level == 1: - proof_detail_list = [] - for proof_detail in response_json: - proof_detail_list.append(self._get_verbose_1_proof_detail(proof_detail)) - print(f"{pformat(proof_detail_list, indent=4)}\n") - - return response_json - def get_circuit(self, circuit_id: str, include_verification_key: bool = True) -> dict: """Get circuit for `circuit_id`.""" if self.verbose_level > 0: diff --git a/tests/test_sindri.py b/tests/test_sindri.py index 6c8522b..dec4c5e 100644 --- a/tests/test_sindri.py +++ b/tests/test_sindri.py @@ -57,9 +57,6 @@ def test_user_team_details(self): def test_get_all_circuits(self): sindri.get_all_circuits() - def test_get_all_proofs(self): - sindri.get_all_proofs() - def test_circuit_create_prove_other(self): """ Most SDK methods require a circuit and a proof to be created. From 737b18938f336c47ca22f6df98ca7c227569258b Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Mon, 25 Mar 2024 23:09:37 +0000 Subject: [PATCH 09/28] overhaul docstrings. these are formatted with an adapted google docstring standard. the adaptation allows our docusaurus site to render them well. --- src/sindri/sindri.py | 374 ++++++++++++++++++++++++++++--------------- 1 file changed, 243 insertions(+), 131 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index b572825..5f9376b 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -1,3 +1,26 @@ +""" +## Usage + +```python +# Download some sample data +# git clone https://github.com/Sindri-Labs/sindri-resources.git + +# pip install sindri +from sindri.sindri import Sindri + +# Initialize +sindri = Sindri("", verbose_level=2) + +# Upload a Circuit +circuit_upload_path: str = "sindri-resources/circuit_database/circom/multiplier2" +circuit_id: str = sindri.create_circuit(circuit_upload_path) + +# Generate a Proof +proof_input_file_path = "sindri-resources/circuit_database/circom/multiplier2/input.json" +with open(proof_input_file_path, "r") as f: + proof_id: str = sindri.prove_circuit(circuit_id, f.read()) +``` +""" import io import json import os @@ -13,31 +36,7 @@ class Sindri: - """# Sindri API SDK - - A utility class for interacting with the [Sindri API](https://www.sindri.app). - - ### Dependencies - - - `requests`: (`pip install requests`) - - ### Usage Example - - ```python - circuit_upload_path = "circom/multiplier2" - proof_input = "" - proof_input_file_path = "circom/multiplier2/input.json" - with open(proof_input_file_path, "r") as f: - proof_input = f.read() - - # Run Sindri API - API_KEY = - sindri = Sindri(API_KEY) - circuit_id = sindri.create_circuit(circuit_upload_path) - proof_id = sindri.prove_circuit(circuit_id, proof_input) - ``` - - """ + """A utility class for interacting with the [Sindri API](https://www.sindri.app).""" class APIError(Exception): """Custom Exception for Sindri API Errors""" @@ -47,18 +46,24 @@ class APIError(Exception): DEFAULT_SINDRI_API_URL = "https://sindri.app/api/" API_VERSION = "v1" - def __init__( - self, - api_key: str, - api_url: str = DEFAULT_SINDRI_API_URL, - verbose_level: int = 0, - ): - """ - Parameters + VERBOSE_LEVELS: list[int] = [0, 1, 2] - - `api_key: str`: Sindri API Key - - `api_url: str`: Sindri API Url - - `verbose_level: int`: Stdout print level. Options=`[0,1,2]` + def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): + """Initialize an instance of the Sindri SDK. + + Args: + - `api_key`: Your Sindri API Key. + - `verbose_level`: Must be either `0`, `1`, or `2`. + - `0`: Do not print anything to stdout. + - `1`: Print only necesessary information from Circuit/Proof objects. + - `2`: Print everything. + + Returns: + - `sindri`: An instance of the class configured with your Sindri API Key. + + Raises: + - `Sindri.APIError`: + - Your API Key is improperly formatted. """ # Obtain version from module self.version = __version__ @@ -71,14 +76,25 @@ def __init__( self.polling_interval_sec: int = 1 # polling interval for circuit compilation & proving self.max_polling_iterations: int = 172800 # 2 days with polling interval 1 second self.perform_verify: bool = False - self.set_api_url(api_url) + + # Set API Url + api_url = kwargs.get("api_url", self.DEFAULT_SINDRI_API_URL) + if not isinstance(api_url, str): + raise Sindri.APIError("Invalid API Url") + if api_url == "": + raise Sindri.APIError("Invalid API Url") + if not api_url.endswith(self.API_VERSION) or not api_url.endswith(f"{self.API_VERSION}/"): + # Append f"{self.API_VERSION}/" to api_url + self._api_url = os.path.join(api_url, f"{self.API_VERSION}/") + + # Set API Key self.set_api_key(api_key) # Set desired verbose level self.set_verbose_level(verbose_level) if self.verbose_level > 0: self._print_sindri_logo() - print(f"Sindri API Url: {self.api_url}") + print(f"Sindri API Url: {self._api_url}") print(f"Sindri API Key: {self.api_key}\n") def _get_circuit(self, circuit_id: str, include_verification_key: bool = False) -> dict: @@ -171,7 +187,7 @@ def _hit_api(self, method: str, path: str, data=None, files=None) -> tuple[int, data = {} # Construct the full path to the API endpoint. - full_path = os.path.join(self.api_url, path) + full_path = os.path.join(self._api_url, path) try: if method == "POST": response = requests.post( @@ -241,26 +257,30 @@ def _set_json_request_headers(self) -> None: def create_circuit( self, circuit_upload_path: str, tags: list[str] | None = None, wait: bool = True ) -> str: - """Create a circuit. + """Create a circuit. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/circuit-create/). Args: - `circuit_upload_path`: The path to a directory containing your circuit files or - the path to a compressed file (`.tar.gz` or `.zip`) of your circuit directory. - If it is a directory, it will automatically be tarred before sending. - `tags`: A list of tags to assign the circuit. Defaults to `[]"latest"]` if not - sepecified. - `wait`: If `True`, block until the circuit is finished compiling. If `False`, this - will submit the circuit and return immediately. + - `circuit_upload_path`: The path to either + - A directory containing your circuit files + - A compressed file (`.tar.gz` or `.zip`) of your circuit directory + - `tags`: A list of tags to assign the circuit. Defaults to `["latest"]` if not + sepecified. + - `wait`: + - If `True`, block until the circuit is finished compiling. + - If `False`, submit the circuit and return immediately. Returns: - `circuit_id`: The unique idetifier for your circuit. This is generated by Sindri. + - `circuit_id`: The UUID4 identifier associated with this circuit. This is generated by + Sindri. Raises: - `Sindri.APIError`: If your API Key is invalid, there is an error connecting to the - Sindri API, there is an error with your circuit upload, or the - circuit has a compilation error (if `wait=True`). + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - There is an error with your circuit upload. + - The circuit has a compilation error (if `wait=True`). """ - # Return value circuit_id = "" # set later @@ -329,11 +349,20 @@ def create_circuit( return circuit_id def delete_circuit(self, circuit_id: str) -> None: - """ - Delete a circuit by hitting the `/circuit//delete` endpoint. - This also deletes all of the circuit's proofs. + """Mark the specified circuit and any of its related proofs as deleted. For information, + refer to the [API docs](https://sindri.app/docs/reference/api/circuit-delete/). + + Args: + - `circuit_id`: The circuit identifier of the circuit. - Returns `None` if the deletion was successful. Else, raise `Sindri.APIError`. + Returns: + - `None` + + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified circuit does not exist. """ response_status_code, response_json = self._hit_api( "DELETE", f"circuit/{circuit_id}/delete" @@ -345,10 +374,20 @@ def delete_circuit(self, circuit_id: str) -> None: ) def delete_proof(self, proof_id: str) -> None: - """ - Delete a proof by hitting the `/proof//delete` endpoint. + """Mark the specified proof as deleted. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/proof-delete/). + + Args: + - `proof_id`: The UUID4 identifier associated with this proof. - Returns `None` if the deletion was successful. Else, raise `Sindri.APIError`. + Returns: + - `None` + + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified proof does not exist. """ response_status_code, response_json = self._hit_api("DELETE", f"proof/{proof_id}/delete") if response_status_code != 200: @@ -358,7 +397,21 @@ def delete_proof(self, proof_id: str) -> None: ) def get_all_circuit_proofs(self, circuit_id: str) -> list[dict]: - """Get all proofs for `circuit_id`.""" + """Return a list of proof infos for the provided circuit_id. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/circuit-proofs/). + + Args: + - `circuit_id`: The circuit identifier of the circuit. + + Returns: + - `proofs`: A list of proof infos. + + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified circuit does not exist. + """ if self.verbose_level > 0: print(f"Proof: Get all proofs for circuit_id: {circuit_id}") response_status_code, response_json = self._hit_api( @@ -384,7 +437,20 @@ def get_all_circuit_proofs(self, circuit_id: str) -> list[dict]: return response_json def get_all_circuits(self) -> list[dict]: - """Get all circuits.""" + """Return a list of all circuit infos. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/circuit-list/). + + Args: + - `None` + + Returns: + - `circuits`: A list of circuit infos. + + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + """ if self.verbose_level > 0: print("Circuit: Get all circuits") response_status_code, response_json = self._hit_api( @@ -410,7 +476,23 @@ def get_all_circuits(self) -> list[dict]: return response_json def get_circuit(self, circuit_id: str, include_verification_key: bool = True) -> dict: - """Get circuit for `circuit_id`.""" + """Get info for an existing circuit. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/circuit-detail/). + + Args: + - `circuit_id`: The circuit identifier of the circuit. + - `include_verification_key`: Indicates whether to include the verification key in the + response. + + Returns: + - `circuit`: The info for a circuit. + + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified circuit does not exist. + """ if self.verbose_level > 0: print(f"Circuit: Get circuit detail for circuit_id: {circuit_id}") circuit = self._get_circuit(circuit_id, include_verification_key=include_verification_key) @@ -422,15 +504,24 @@ def get_circuit(self, circuit_id: str, include_verification_key: bool = True) -> return circuit def get_smart_contract_verifier(self, circuit_id: str) -> str: - """Get the circuit's smart contract verifier code for `circuit_id`. + """Get the smart contract verifier for an existing circuit. - Returns: + NOTE: This method wraps an experimental Sindri API endpoint is subject to change at + any time. - - `str`: the smart contract verifier code + Args: + - `circuit_id`: The circuit identifier of the circuit. - Raises `Sindri.APIError` if: + Returns: + - `smart_contract_verifier_code`: The smart contract verifier code for the circuit. - - the circuit's circuit type does not support smart contract verifier generation + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified circuit does not exist. + - The circuit's type does not support this feature. + - The circuit was compiled before this feature was released. """ if self.verbose_level > 0: print(f"Circuit: Get circuit smart contract verifier for circuit_id: {circuit_id}") @@ -471,18 +562,29 @@ def get_proof( include_smart_contract_calldata: bool = False, include_verification_key: bool = False, ) -> dict: - """ - Get proof for `proof_id`. + """Get info for an existing proof. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/proof-detail/). - Parameters: - - - `include_smart_contract_calldata: bool` specifies if the proof + public formated as - calldata for the circuit's smart contract verifier should also be returned. + Args: + - `proof_id`: The UUID4 identifier associated with this proof. + - `include_proof`: Indicates whether to include the proof in the response. + - `include_public`: Indicates whether to include the public inputs in the response. + - `include_smart_contract_calldata`: Indicates whether to include the proof and public + formatted as smart contract calldata in the response. + - `include_verification_key`: Indicates whether to include the verification key in the + response. - Raises `Sindri.APIError` if: + Returns: + - `proof`: The info for a proof. - - `include_smart_contract_calldata=True` and the proof's circuit type does not support - generating calldata for its circuit's smart contract verifier + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified proof does not exist. + - `include_smart_contract_calldata=True` and the proof's circuit type does not support + generating calldata for its circuit's smart contract verifier or the proof was + generated before this feature was released. """ if self.verbose_level > 0: print(f"Proof: Get proof detail for proof_id: {proof_id}") @@ -501,7 +603,19 @@ def get_proof( return proof def get_user_team_details(self) -> dict: - """Get details about the user or team associated with the provided API Key.""" + """Get details about the user or team associated with the configured API Key. + + Args: + - `None` + + Returns: + - `team`: The info for the user/team. + + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + """ if self.verbose_level > 0: print("User/Team: Get user/team details for the provided API Key.") response_status_code, response_json = self._hit_api("GET", "team/me") @@ -526,26 +640,33 @@ def prove_circuit( perform_verify: bool = False, wait: bool = True, ) -> str: - """ - Prove a circuit given a `circuit_id` and a `proof_input` and, if `wait=True`, poll the - detail endpoint until the proof has a `status` of `Ready` or `Failed`. + """Prove a circuit with specified inputs. For information, refer to the + [API docs](https://sindri.app/docs/reference/api/proof-create/). - Return - - str: proof_id - - Raises `Sindri.APIError` if + Args: + - `circuit_id`: The circuit identifier of the circuit. + - `proof_input`: A string representing proof input which may be formatted as JSON for any + framework. Noir circuits optionally accept TOML formatted proof input. + - `prover_implementation`: For Sindri internal usage. The default value, `None`, chooses + the best supported prover implementation based on a variety of factors including the + available hardware, proving scheme, circuit size, and framework. + - `perform_verify`: A boolean indicating whether to perform an internal verification check + during the proof creation. + - `wait`: + - If `True`, block until the proof is finished generating. + - If `False`, submit the proof and return immediately. - - Invalid API Key - - Unable to connect to the API - - Circuit does not exist - - If `wait=True`, circuit does not have a `status` of `Ready` + Returns: + - `proof_id`: The UUID4 identifier associated with this proof. This is generated by Sindri. - NOTE: `prover_implementation` is currently only supported for Sindri internal usage. - The default value, `None`, chooses the best supported prover implementation based on a - variety of factors including the available hardware, proving scheme, circuit size, and - framework. + Raises: + - `Sindri.APIError`: + - Your API Key is invalid. + - There is an error connecting to the Sindri API. + - The specified circuit does not exist. + - The proof input is improperly formatted. + - The proof generation fails. (if `wait=True`). """ - # Return values proof_id = "" @@ -602,7 +723,18 @@ def prove_circuit( return proof_id def set_api_key(self, api_key: str) -> None: - """Set the API Key and headers for the Sindri instance.""" + """Set the API Key for the Sindri instance. + + Args: + - `api_key`: Your Sindri API Key. + + Returns: + - `None` + + Raises: + - `Sindri.APIError`: + - Your API Key is improperly formatted. + """ if not isinstance(api_key, str): raise Sindri.APIError("Invalid API Key") if api_key == "": @@ -612,44 +744,24 @@ def set_api_key(self, api_key: str) -> None: if self.verbose_level > 0: print(f"Sindri API Key: {self.api_key}") - def set_api_url(self, api_url: str) -> None: - """ - Set the API Url for the Sindri instance. + def set_verbose_level(self, verbose_level: int) -> None: + """Set the verbosity level for stdout printing. - NOTE: `v1/` is appended to the Sindri API Url if it is not present. - - Example: `https://sindri.app/api/` becomes `https://sindri.app/api/v1/` - """ - if not isinstance(api_url, str): - raise Sindri.APIError("Invalid API Url") - if api_url == "": - raise Sindri.APIError("Invalid API Url") - if not api_url.endswith(self.API_VERSION) or not api_url.endswith(f"{self.API_VERSION}/"): - # Append f"{self.API_VERSION}/" to api_url - self.api_url = os.path.join(api_url, f"{self.API_VERSION}/") - if self.verbose_level > 0: - print(f"Sindri API Url: {self.api_url}") - - def set_verbose_level(self, level: int) -> None: - """ - Set the verbosity level for stdout printing. - - Parameters: - - - `level: int` must be in `[0,1,2]` - - Levels: - - - `0`: Do not print anything to stdout - - `1`: Print only necesessary information from Circuit/Proof objects - - `2`: Print everything + Args: + - `verbose_level`: Must be either `0`, `1`, or `2`. + - `0`: Do not print anything to stdout. + - `1`: Print only necesessary information from Circuit/Proof objects. + - `2`: Print everything. - Raises `Sindri.APIError` if + Returns: + - `None` - - `level` is invalid. + Raises: + - `Sindri.APIError`: + - `verbose_level` is invalid. """ - error_msg = "Invalid verbose_level. Must be an int in [0,1,2]." - if not isinstance(level, int): - raise Sindri.APIError(error_msg) - elif level not in [0, 1, 2]: - raise Sindri.APIError(error_msg) - self.verbose_level = level + if verbose_level not in Sindri.VERBOSE_LEVELS: + raise Sindri.APIError( + f"Invalid verbose_level. Must be an int in {Sindri.VERBOSE_LEVELS}." + ) + self.verbose_level = verbose_level From d23516cbfe42e94e4061572bc86dc26eef1279d7 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Mon, 25 Mar 2024 23:18:59 +0000 Subject: [PATCH 10/28] remove unnecessary line from test --- tests/test_sindri.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_sindri.py b/tests/test_sindri.py index dec4c5e..67f5940 100644 --- a/tests/test_sindri.py +++ b/tests/test_sindri.py @@ -134,5 +134,4 @@ def test_circuit_create_prove_smart_contract_calldata(self): proof_id = sindri.prove_circuit(circuit_id, circom_proof_input) sindri.get_smart_contract_verifier(circuit_id) sindri.get_proof(proof_id, include_smart_contract_calldata=True) - sindri.delete_proof(proof_id) sindri.delete_circuit(circuit_id) From 3de62f2ea608c4897de8664ec01485b0972a2107 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Mon, 25 Mar 2024 23:20:27 +0000 Subject: [PATCH 11/28] remove random dev files --- test.py | 23 ----------------------- test2.py | 19 ------------------- 2 files changed, 42 deletions(-) delete mode 100644 test.py delete mode 100644 test2.py diff --git a/test.py b/test.py deleted file mode 100644 index 7429f4c..0000000 --- a/test.py +++ /dev/null @@ -1,23 +0,0 @@ -import sys - - -class A: - def __init__(self): - pass - - def foo(self): - # Do not print if the caller was A.bar() - print(sys._getframe().f_back.f_code.co_name) - print("foo()") - - def bar(self): - print("bar()") - - self.foo() - - -# print sys._getframe().f_back.f_code.co_name - -a = A() -a.foo() -a.bar() diff --git a/test2.py b/test2.py deleted file mode 100644 index 788506c..0000000 --- a/test2.py +++ /dev/null @@ -1,19 +0,0 @@ -import inspect -from types import FrameType -from typing import cast - - -def demo_the_caller_name() -> str: - """Return the calling function's name.""" - # Ref: https://stackoverflow.com/a/57712700/ - caller_name = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name - print(caller_name) - return caller_name - - -if __name__ == "__main__": - - def _test_caller_name() -> None: - assert demo_the_caller_name() == "_test_caller_name" - - _test_caller_name() From 7f8d614a079d5e2bf80fafb7b3ed4f7e484fd639 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Mon, 25 Mar 2024 23:30:29 +0000 Subject: [PATCH 12/28] add helpful command to tests/README.md --- tests/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/README.md b/tests/README.md index e2d2841..8759380 100644 --- a/tests/README.md +++ b/tests/README.md @@ -46,3 +46,9 @@ Prefixing ENVs: ```bash SINDRI_API_KEY=your_api_key SINDRI_API_URL=https://sindri.app/api pytest ``` + +For Sindri internal developers: +- If you are running the sindri api locally at ~/forge with ~/forge/API_KEY file populated with a valid api key +```bash +SINDRI_API_URL=http://localhost/api SINDRI_API_KEY=$(cat ~/forge/API_KEY) pytest +``` \ No newline at end of file From 22a7b93c019b0db0452032c9cbb2aa64c5bdb9bb Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Tue, 26 Mar 2024 17:09:20 +0000 Subject: [PATCH 13/28] merge main, lint --- src/sindri/__init__.py | 3 ++- src/sindri/sindri.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sindri/__init__.py b/src/sindri/__init__.py index e913da4..22a15ee 100644 --- a/src/sindri/__init__.py +++ b/src/sindri/__init__.py @@ -1,5 +1,6 @@ from .sindri import Sindri, __version__ -__all__ = ['Sindri'] + +__all__ = ["Sindri"] __author__ = "Sindri Labs LLC" __email__ = "hello@sindri.app" __license__ = "MIT" diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 2ec45f5..4852178 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -32,9 +32,9 @@ import requests - __version__ = "v0.0.0" + class Sindri: """A utility class for interacting with the [Sindri API](https://www.sindri.app).""" From 3c17bec522e521df13e743a968483e2369cb4547 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 17:39:38 +0000 Subject: [PATCH 14/28] update docstring with new import path --- src/sindri/sindri.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 4852178..8ab6836 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -6,7 +6,7 @@ # git clone https://github.com/Sindri-Labs/sindri-resources.git # pip install sindri -from sindri.sindri import Sindri +from sindri import Sindri # Initialize sindri = Sindri("", verbose_level=2) From 3c15efaf9e96fdcb54210c4d98a424c4ad6d6d02 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 17:46:01 +0000 Subject: [PATCH 15/28] update readme instruction --- tests/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/README.md b/tests/README.md index 8759380..49583b4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -33,22 +33,22 @@ When running `pytest`, these environment variables must be set in your environme ### Run Pytest You must be in the root directory of this repo: `cd ..` -Here are several options for running tests with environment variables: +Here are several options for running the pytest unit tests with environment variables: -Exporting ENVs: +#### Exporting ENVs ```bash export SINDRI_API_KEY=your_api_key export SINDRI_API_URL=https://sindri.app/api pytest ``` -Prefixing ENVs: +#### Prefixing ENVs ```bash SINDRI_API_KEY=your_api_key SINDRI_API_URL=https://sindri.app/api pytest ``` -For Sindri internal developers: -- If you are running the sindri api locally at ~/forge with ~/forge/API_KEY file populated with a valid api key +#### Prefixing ENVs for Sindri internal developers +If you are running the Sindri api locally at `~/forge` with `~/forge/API_KEY` file populated with a valid api key, you can invoke the `pytest` unit tests to hit your local Sindri API instance with: ```bash SINDRI_API_URL=http://localhost/api SINDRI_API_KEY=$(cat ~/forge/API_KEY) pytest -``` \ No newline at end of file +``` From 385705a01838e7bd0a8a4e3b1d4095c7e06634af Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 17:53:53 +0000 Subject: [PATCH 16/28] fix api_url setting --- src/sindri/sindri.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 8ab6836..e6e4b1e 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -78,14 +78,16 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): self.perform_verify: bool = False # Set API Url - api_url = kwargs.get("api_url", self.DEFAULT_SINDRI_API_URL) - if not isinstance(api_url, str): + self._api_url = kwargs.get("api_url", self.DEFAULT_SINDRI_API_URL) + if not isinstance(self._api_url, str): raise Sindri.APIError("Invalid API Url") - if api_url == "": + if self._api_url == "": raise Sindri.APIError("Invalid API Url") - if not api_url.endswith(self.API_VERSION) or not api_url.endswith(f"{self.API_VERSION}/"): - # Append f"{self.API_VERSION}/" to api_url - self._api_url = os.path.join(api_url, f"{self.API_VERSION}/") + if not self._api_url.endswith(self.API_VERSION) or not self._api_url.endswith( + f"{self.API_VERSION}/" + ): + # Append f"{self.API_VERSION}/" to self._api_url + self._api_url = os.path.join(self._api_url, f"{self.API_VERSION}/") # Set API Key self.set_api_key(api_key) From 63c9766bd362fb9816e2280fe8c887c1664572d0 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 20:07:01 +0000 Subject: [PATCH 17/28] overhaul api_url processing. add base_url option to kwargs --- src/sindri/sindri.py | 69 ++++++++++++++++++++++----- tests/README.md | 2 +- tests/test_sindri.py | 110 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 166 insertions(+), 15 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index e6e4b1e..7214462 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -29,6 +29,7 @@ import tarfile import time from pprint import pformat +from urllib.parse import urlparse import requests @@ -43,8 +44,7 @@ class APIError(Exception): pass - DEFAULT_SINDRI_API_URL = "https://sindri.app/api/" - API_VERSION = "v1" + DEFAULT_SINDRI_API_URL = "https://sindri.app/api/v1/" VERBOSE_LEVELS: list[int] = [0, 1, 2] @@ -78,16 +78,7 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): self.perform_verify: bool = False # Set API Url - self._api_url = kwargs.get("api_url", self.DEFAULT_SINDRI_API_URL) - if not isinstance(self._api_url, str): - raise Sindri.APIError("Invalid API Url") - if self._api_url == "": - raise Sindri.APIError("Invalid API Url") - if not self._api_url.endswith(self.API_VERSION) or not self._api_url.endswith( - f"{self.API_VERSION}/" - ): - # Append f"{self.API_VERSION}/" to self._api_url - self._api_url = os.path.join(self._api_url, f"{self.API_VERSION}/") + self._api_url = self._get_api_url_from_init_kwargs(**kwargs) # Set API Key self.set_api_key(api_key) @@ -99,6 +90,60 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): print(f"Sindri API Url: {self._api_url}") print(f"Sindri API Key: {self.api_key}\n") + def _get_api_url_from_init_kwargs(self, **kwargs) -> str: + """Given the `kwargs` from the `__init__` method, return the api url. + This looks for `api_url` and `base_url` in `**kwargs`. `base_url` is processed first, + then `api_url` if `base_url` is not present. Finally, if neither are present, this will + return the default api url. + """ + + def is_url(url: str) -> bool: + """Validate a provided str is a url.""" + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + # Order of precedence for setting self._api_url + # 1. `base_url` is in kwargs + url = kwargs.get("base_url", None) + if url is not None: + error_msg: str = "Invalid 'base_url' provided." + if not isinstance(url, str): + raise Sindri.APIError(error_msg) + # Remove trailing slash "/" + url = url.rstrip("/") + # Validate str is a url + if not is_url(url): + raise Sindri.APIError(error_msg) + # We assume a provided "base_url" has no path on the end + path = urlparse(url).path + if path != "": + raise Sindri.APIError(error_msg) + return os.path.join(url, "api", "v1", "") + + # 2. `api_url` is in kwargs + url = kwargs.get("api_url", None) + if url is not None: + print("\nWARNING: 'api_url' is deprecated. Use 'base_url'.\n") + error_msg = "Invalid 'api_url' provided." + if not isinstance(url, str): + raise Sindri.APIError(error_msg) + # Remove trailing slash "/" + url = url.rstrip("/") + # Validate str is a url + if not is_url(url): + raise Sindri.APIError(error_msg) + # We assume a provided "api_url" has "/api" as the path on the end + path = urlparse(url).path + if path != "/api": + raise Sindri.APIError(error_msg) + return os.path.join(url, "v1", "") + + # 3. Use default + return self.DEFAULT_SINDRI_API_URL + def _get_circuit(self, circuit_id: str, include_verification_key: bool = False) -> dict: """Hit the circuit_detail API endpoint and validate the response. Do not print anything. This may raise `Sindri.APIError` if the response is invalid.""" diff --git a/tests/README.md b/tests/README.md index 49583b4..d9050ad 100644 --- a/tests/README.md +++ b/tests/README.md @@ -50,5 +50,5 @@ SINDRI_API_KEY=your_api_key SINDRI_API_URL=https://sindri.app/api pytest #### Prefixing ENVs for Sindri internal developers If you are running the Sindri api locally at `~/forge` with `~/forge/API_KEY` file populated with a valid api key, you can invoke the `pytest` unit tests to hit your local Sindri API instance with: ```bash -SINDRI_API_URL=http://localhost/api SINDRI_API_KEY=$(cat ~/forge/API_KEY) pytest +SINDRI_BASE_URL=http://localhost SINDRI_API_KEY=$(cat ~/forge/API_KEY) pytest ``` diff --git a/tests/test_sindri.py b/tests/test_sindri.py index 67f5940..d2ef869 100644 --- a/tests/test_sindri.py +++ b/tests/test_sindri.py @@ -1,6 +1,8 @@ import os import time +import pytest + from src.sindri.sindri import Sindri # Load sample data. @@ -33,8 +35,8 @@ # Create an instance of the Sindri SDK api_key = os.environ.get("SINDRI_API_KEY", "unset") -api_url = os.environ.get("SINDRI_API_URL", "https://sindri.app/api") -sindri = Sindri(api_key, api_url=api_url, verbose_level=2) +base_url = os.environ.get("SINDRI_BASE_URL", "https://sindri.app") +sindri = Sindri(api_key, base_url=base_url, verbose_level=2) class TestSindriSdk: @@ -135,3 +137,107 @@ def test_circuit_create_prove_smart_contract_calldata(self): sindri.get_smart_contract_verifier(circuit_id) sindri.get_proof(proof_id, include_smart_contract_calldata=True) sindri.delete_circuit(circuit_id) + + +class TestInitSindriSdkWithUrl: + def test_api_url(self): + """Test valid and invalid api_url.""" + expected_resulting_api_url = "https://sindri.app/api/v1/" + + # No trailing slash + url = "https://sindri.app/api" + s = Sindri("a", api_url=url) + assert s._api_url == expected_resulting_api_url + + # Trailing slash + url = "https://sindri.app/api/" + s = Sindri("a", api_url=url) + assert s._api_url == expected_resulting_api_url + + # Multiple trailing slashes + url = "https://sindri.app/api//" + s = Sindri("a", api_url=url) + assert s._api_url == expected_resulting_api_url + + # Missing trailing /api path + url = "https://sindri.app" + with pytest.raises(Sindri.APIError): + s = Sindri("a", api_url=url) + + # Invalid trailing /api path + url = "https://sindri.app/api/apiiii" + with pytest.raises(Sindri.APIError): + s = Sindri("a", api_url=url) + + def test_base_url(self): + """Test valid and invalid base_url.""" + expected_resulting_api_url = "https://sindri.app/api/v1/" + + # No trailing slash + url = "https://sindri.app" + s = Sindri("a", base_url=url) + assert s._api_url == expected_resulting_api_url + + # Trailing slash + url = "https://sindri.app/" + s = Sindri("a", base_url=url) + assert s._api_url == expected_resulting_api_url + + # Multiple trailing slashes + url = "https://sindri.app//" + s = Sindri("a", base_url=url) + assert s._api_url == expected_resulting_api_url + + # Includes /api trailing path + url = "https://sindri.app/api" + with pytest.raises(Sindri.APIError): + s = Sindri("a", base_url=url) + + # Invalid any trailing path + url = "https://sindri.app/apiiii" + with pytest.raises(Sindri.APIError): + s = Sindri("a", base_url=url) + + def test_api_url_and_base_url(self): + """Test valid and invalid api_url + base_url.""" + + # Valid api_url and valid base_url should return base_url + expected_resulting_api_url = "https://base-url.sindri.app/api/v1/" + api_url = "https://api-url.sindri.app/api" + base_url = "https://base-url.sindri.app" + s = Sindri("a", api_url=api_url, base_url=base_url) + assert s._api_url == expected_resulting_api_url + + # Invalid api_url and valid base_url should return base_url. + # api_url is ignored because base_url is prioritized + expected_resulting_api_url = "https://base-url.sindri.app/api/v1/" + api_url = "https://api-url.sindri.app/apiiiiii" + base_url = "https://base-url.sindri.app" + s = Sindri("a", api_url=api_url, base_url=base_url) + assert s._api_url == expected_resulting_api_url + + # Valid api_url and invalid base_url should raise exception + # base_url is prioritized + api_url = "https://api-url.sindri.app/apiiiiii" + base_url = "https://base-url.sindri.app/aaaaaaaa" + with pytest.raises(Sindri.APIError): + s = Sindri("a", api_url=api_url, base_url=base_url) + + # Invalid api_url and invalid base_url should raise exception + api_url = "https://api-url.sindri.app/apiiiiii" + base_url = "https://base-url.sindri.app/aaaaaaaa" + with pytest.raises(Sindri.APIError): + s = Sindri("a", api_url=api_url, base_url=base_url) + + def test_default_url(self): + """Test default url is used when api_url and base_url are not specified.""" + s = Sindri("a") + assert s.DEFAULT_SINDRI_API_URL == s._api_url + + def test_not_a_url(self): + """Test api_url base_url are not urls.""" + with pytest.raises(Sindri.APIError): + _ = Sindri("a", api_url="not_a_url") + + with pytest.raises(Sindri.APIError): + _ = Sindri("a", base_url="not_a_url") From f4deea46a0595fefb565c02be65f2ea559f9cd40 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 22:10:28 +0000 Subject: [PATCH 18/28] use base url in readme --- tests/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/README.md b/tests/README.md index d9050ad..c409ee4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -28,7 +28,7 @@ poetry install When running `pytest`, these environment variables must be set in your environment. You may prefix your command with the environment variables as well. For example: `SINDRI_API_KEY=your_api_key pytest` - `SINDRI_API_KEY=your_api_key` *Required* -- `SINDRI_API_URL=https://sindri.app/api` *Optional. Default value shown* +- `SINDRI_BASE_URL=https://sindri.app` *Optional. Default value shown* ### Run Pytest You must be in the root directory of this repo: `cd ..` @@ -38,13 +38,13 @@ Here are several options for running the pytest unit tests with environment vari #### Exporting ENVs ```bash export SINDRI_API_KEY=your_api_key -export SINDRI_API_URL=https://sindri.app/api +export SINDRI_BASE_URL=https://sindri.app pytest ``` #### Prefixing ENVs ```bash -SINDRI_API_KEY=your_api_key SINDRI_API_URL=https://sindri.app/api pytest +SINDRI_API_KEY=your_api_key SINDRI_BASE_URL=https://sindri.app pytest ``` #### Prefixing ENVs for Sindri internal developers From 55e6b81d0394ead570bdc8790f5408a390d5761d Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Wed, 27 Mar 2024 17:16:28 -0600 Subject: [PATCH 19/28] improve comments & docstrings --- src/sindri/sindri.py | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 7214462..831ff78 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -70,20 +70,17 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): self.headers_json: dict = {} # set in set_api_key() - # Do not print anything during initial setup + # Do not print anything during initial setup. self.set_verbose_level(0) self.polling_interval_sec: int = 1 # polling interval for circuit compilation & proving self.max_polling_iterations: int = 172800 # 2 days with polling interval 1 second self.perform_verify: bool = False - # Set API Url self._api_url = self._get_api_url_from_init_kwargs(**kwargs) - - # Set API Key self.set_api_key(api_key) - # Set desired verbose level + # With initial setup complete, set desired verbose level. self.set_verbose_level(verbose_level) if self.verbose_level > 0: self._print_sindri_logo() @@ -91,10 +88,9 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): print(f"Sindri API Key: {self.api_key}\n") def _get_api_url_from_init_kwargs(self, **kwargs) -> str: - """Given the `kwargs` from the `__init__` method, return the api url. - This looks for `api_url` and `base_url` in `**kwargs`. `base_url` is processed first, - then `api_url` if `base_url` is not present. Finally, if neither are present, this will - return the default api url. + """Receive `kwargs` from `Sindri.__init__()` and return the API URL. + This examines `**kwargs` for `base_url` and `api_url`, in that order. + If neither are present, return the default API URL. """ def is_url(url: str) -> bool: @@ -105,14 +101,14 @@ def is_url(url: str) -> bool: except ValueError: return False - # Order of precedence for setting self._api_url + # Order of precedence for obtaining the API URL: # 1. `base_url` is in kwargs url = kwargs.get("base_url", None) if url is not None: error_msg: str = "Invalid 'base_url' provided." if not isinstance(url, str): raise Sindri.APIError(error_msg) - # Remove trailing slash "/" + # Remove all trailing slashes "/" url = url.rstrip("/") # Validate str is a url if not is_url(url): @@ -126,11 +122,11 @@ def is_url(url: str) -> bool: # 2. `api_url` is in kwargs url = kwargs.get("api_url", None) if url is not None: - print("\nWARNING: 'api_url' is deprecated. Use 'base_url'.\n") + print("\nWARNING: 'api_url' is deprecated. Please use 'base_url' instead.\n") error_msg = "Invalid 'api_url' provided." if not isinstance(url, str): raise Sindri.APIError(error_msg) - # Remove trailing slash "/" + # Remove all trailing slashes "/" url = url.rstrip("/") # Validate str is a url if not is_url(url): @@ -276,18 +272,18 @@ def _print_sindri_logo(self): # https://ascii-generator.site/ 32 columns print( f"""Sindri API Python SDK - {self.version} - .+******************+. - =********************= - .:. -==================- -=**** -=****- - .::-*+==================- - =********************= - .+******************+. + .+******************+. + =********************= + .:. -==================- +=**** +=****- + .::-*+==================- + =********************= + .+******************+. =**+: :***** - .:::::::::::::::::::::++==- - .***********************+ + .:::::::::::::::::::::++==- + .***********************+ .***********************-""" ) From 201f2b7382ad99a6021fb8f52ea1e59970235493 Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Wed, 27 Mar 2024 17:19:50 -0600 Subject: [PATCH 20/28] update dev dependencies: black, flake8 --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0128790..6502ca5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,9 +46,8 @@ lazydocs = "^0.4.8" mkdocs = "^1.5.3" mkdocs-awesome-pages-plugin = "^2.9.2" pytest = "^8.1.1" -black = "23.7.0" -flake8 = "6.1.0" +black = "24.2.0" +flake8 = "7.0.0" isort = "5.10.1" mypy = "^1.9.0" types-requests = "^2.31.0.20240311" - From 478eddec0fbd6c3f39c0deae02e6a6d43398d54e Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Wed, 27 Mar 2024 17:47:32 -0600 Subject: [PATCH 21/28] remove duplicate var assignment --- tests/test_sindri.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_sindri.py b/tests/test_sindri.py index d2ef869..95d32b8 100644 --- a/tests/test_sindri.py +++ b/tests/test_sindri.py @@ -210,7 +210,6 @@ def test_api_url_and_base_url(self): # Invalid api_url and valid base_url should return base_url. # api_url is ignored because base_url is prioritized - expected_resulting_api_url = "https://base-url.sindri.app/api/v1/" api_url = "https://api-url.sindri.app/apiiiiii" base_url = "https://base-url.sindri.app" s = Sindri("a", api_url=api_url, base_url=base_url) From 83fef0ac3e63ccc2b281075a5ede1963c31a2b70 Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Wed, 27 Mar 2024 17:52:31 -0600 Subject: [PATCH 22/28] ignore requests typing --- src/sindri/sindri.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 831ff78..54a4654 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -31,7 +31,7 @@ from pprint import pformat from urllib.parse import urlparse -import requests +import requests # type: ignore __version__ = "v0.0.0" From b581efccee8334a4b398fe4cd1850da37f160816 Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 23:54:41 +0000 Subject: [PATCH 23/28] update poetry lock file --- poetry.lock | 78 ++++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/poetry.lock b/poetry.lock index fde6cf9..bfaa425 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,33 +2,33 @@ [[package]] name = "black" -version = "23.7.0" +version = "24.2.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, + {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, + {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, + {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, + {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, + {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, + {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, + {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, + {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, + {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, + {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, + {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, + {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, + {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, + {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, + {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, + {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, + {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, + {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, + {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, + {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, + {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, + {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, ] [package.dependencies] @@ -38,10 +38,11 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -207,19 +208,19 @@ test = ["pytest (>=6)"] [[package]] name = "flake8" -version = "6.1.0" +version = "7.0.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, - {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, + {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, + {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" -pyflakes = ">=3.1.0,<3.2.0" +pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "ghp-import" @@ -601,13 +602,13 @@ files = [ [[package]] name = "pyflakes" -version = "3.1.0" +version = "3.2.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" files = [ - {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, - {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] @@ -765,24 +766,21 @@ files = [ [[package]] name = "typer" -version = "0.9.0" +version = "0.11.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, - {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, + {file = "typer-0.11.0-py3-none-any.whl", hash = "sha256:049cc47bef39f46b043eddd9165492209fdd9bc7d79afa7ba9cc5cd017caa817"}, + {file = "typer-0.11.0.tar.gz", hash = "sha256:a6ce173c0f03d3a41b49c0a945874cc489e91f88faabf76517b2b91c670fcde7"}, ] [package.dependencies] -click = ">=7.1.1,<9.0.0" +click = ">=8.0.0" typing-extensions = ">=3.7.4.3" [package.extras] all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] -dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] -test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] [[package]] name = "types-requests" @@ -884,4 +882,4 @@ bracex = ">=2.1.1" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "9b4b7b55dcefc487f8044011c7a5e544c3d9b7bb8a80efe061c9b63c23357a63" +content-hash = "499be574d6f02cf2d2e6656afde72387cc5124acb2f515d0d60c0d936ba773dd" From ed686d36569ec99e7ab8274b4ee73b836592146d Mon Sep 17 00:00:00 2001 From: Karl Preisner Date: Wed, 27 Mar 2024 23:58:12 +0000 Subject: [PATCH 24/28] black! --- src/sindri/sindri.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 54a4654..c7fad81 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -21,6 +21,7 @@ proof_id: str = sindri.prove_circuit(circuit_id, f.read()) ``` """ + import io import json import os From d346c1973c106e46f67c58055d2f7fc26ccf4226 Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Thu, 28 Mar 2024 12:15:39 -0600 Subject: [PATCH 25/28] clarify README for internal devs --- tests/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/README.md b/tests/README.md index c409ee4..498694d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -48,7 +48,7 @@ SINDRI_API_KEY=your_api_key SINDRI_BASE_URL=https://sindri.app pytest ``` #### Prefixing ENVs for Sindri internal developers -If you are running the Sindri api locally at `~/forge` with `~/forge/API_KEY` file populated with a valid api key, you can invoke the `pytest` unit tests to hit your local Sindri API instance with: +If you are running the Sindri api locally at `~/myproject` with `~/myproject/API_KEY` file populated with a valid api key, you can invoke the `pytest` unit tests to hit your local Sindri API instance with: ```bash -SINDRI_BASE_URL=http://localhost SINDRI_API_KEY=$(cat ~/forge/API_KEY) pytest +SINDRI_BASE_URL=http://localhost SINDRI_API_KEY=$(cat ~/myproject/API_KEY) pytest ``` From a583a5c85b8e2ea74ffccbe6ba268f9de168e415 Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Thu, 28 Mar 2024 12:25:08 -0600 Subject: [PATCH 26/28] improve docstring and method name --- src/sindri/sindri.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index c7fad81..23b6b2b 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -78,7 +78,7 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): self.max_polling_iterations: int = 172800 # 2 days with polling interval 1 second self.perform_verify: bool = False - self._api_url = self._get_api_url_from_init_kwargs(**kwargs) + self._api_url = self._get_api_url(**kwargs) self.set_api_key(api_key) # With initial setup complete, set desired verbose level. @@ -88,10 +88,12 @@ def __init__(self, api_key: str, verbose_level: int = 0, **kwargs): print(f"Sindri API Url: {self._api_url}") print(f"Sindri API Key: {self.api_key}\n") - def _get_api_url_from_init_kwargs(self, **kwargs) -> str: - """Receive `kwargs` from `Sindri.__init__()` and return the API URL. - This examines `**kwargs` for `base_url` and `api_url`, in that order. - If neither are present, return the default API URL. + def _get_api_url(self, **kwargs) -> str: + """Examine `**kwargs` for `base_url` and `api_url`, in that order. + If `base_url` is found and valid, return that. If not valid, raise an error. + If `base_url` is not found then check for `api_url`. + If `api_url` is found and valid, return that. If not valid, raise an error. + If neither keyword is found in `**kwargs`, return the default API URL. """ def is_url(url: str) -> bool: From c708cb7da616c9cf1132d9a4511fa91d6f59b236 Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Thu, 28 Mar 2024 12:43:15 -0600 Subject: [PATCH 27/28] improve input data checking (api key) --- src/sindri/sindri.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index 23b6b2b..fe066ee 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -31,6 +31,7 @@ import time from pprint import pformat from urllib.parse import urlparse +from uuid import UUID import requests # type: ignore @@ -785,6 +786,14 @@ def set_api_key(self, api_key: str) -> None: raise Sindri.APIError("Invalid API Key") if api_key == "": raise Sindri.APIError("Invalid API Key") + try: + # Ensure `apikey` is a uuid4. + # We type `apikey` as `str` above; if the caller provides a non-uuid4 + # string, we catch that mistake here. + _ = UUID(api_key, version=4) + except ValueError: + raise Sindri.APIError("Invalid API Key") + self.api_key = api_key self._set_json_request_headers() if self.verbose_level > 0: From 8c3463da166af38b7b9f362f3dbf6afb278f435e Mon Sep 17 00:00:00 2001 From: Graham Ullrich Date: Thu, 28 Mar 2024 14:11:25 -0600 Subject: [PATCH 28/28] Revert "improve input data checking (api key)" The apikey passed in is not a UUID Django PK! This reverts commit c708cb7da616c9cf1132d9a4511fa91d6f59b236. --- src/sindri/sindri.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/sindri/sindri.py b/src/sindri/sindri.py index fe066ee..23b6b2b 100644 --- a/src/sindri/sindri.py +++ b/src/sindri/sindri.py @@ -31,7 +31,6 @@ import time from pprint import pformat from urllib.parse import urlparse -from uuid import UUID import requests # type: ignore @@ -786,14 +785,6 @@ def set_api_key(self, api_key: str) -> None: raise Sindri.APIError("Invalid API Key") if api_key == "": raise Sindri.APIError("Invalid API Key") - try: - # Ensure `apikey` is a uuid4. - # We type `apikey` as `str` above; if the caller provides a non-uuid4 - # string, we catch that mistake here. - _ = UUID(api_key, version=4) - except ValueError: - raise Sindri.APIError("Invalid API Key") - self.api_key = api_key self._set_json_request_headers() if self.verbose_level > 0: