From 8cda03b78d0dada65505a81707ab90c27770a9cc Mon Sep 17 00:00:00 2001 From: EmadAnwer Date: Fri, 24 May 2024 09:55:35 +0300 Subject: [PATCH 01/31] Add support for revocable credentials in vc_di handler Signed-off-by: EmadAnwer --- aries_cloudagent/anoncreds/revocation.py | 206 ++++++++++++++++++ .../v2_0/formats/vc_di/handler.py | 28 ++- 2 files changed, 226 insertions(+), 8 deletions(-) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 80e7d8c16e..9ee358b5d8 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -20,6 +20,7 @@ RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate, RevocationStatusList, + W3cCredential, ) from aries_askar.error import AskarError from requests import RequestException, Session @@ -41,6 +42,7 @@ CATEGORY_CRED_DEF_PRIVATE, STATE_FINISHED, AnonCredsIssuer, + AnonCredsIssuerError, ) from .models.anoncreds_revocation import ( RevList, @@ -720,6 +722,9 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef): backoff=-0.5, max_attempts=5, # heuristic: respect HTTP timeout ) + print("#1 tails_server", upload_success) + print("#2 tails_server", result) + if not upload_success: raise AnonCredsRevocationError( f"Tails file for rev reg for {rev_reg_def.cred_def_id} " @@ -895,6 +900,207 @@ async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResu # Credential Operations + async def _create_credential_w3c( + self, + credential_definition_id: str, + schema_attributes: List[str], + credential_offer: dict, + credential_request: dict, + credential_values: dict, + rev_reg_def_id: Optional[str] = None, + tails_file_path: Optional[str] = None, + ) -> Tuple[str, str]: + try: + async with self.profile.session() as session: + cred_def = await session.handle.fetch( + CATEGORY_CRED_DEF, credential_definition_id + ) + cred_def_private = await session.handle.fetch( + CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + ) + except AskarError as err: + raise AnonCredsRevocationError( + "Error retrieving credential definition" + ) from err + if not cred_def or not cred_def_private: + raise AnonCredsRevocationError( + "Credential definition not found for credential issuance" + ) + + raw_values = {} + for attribute in schema_attributes: + # Ensure every attribute present in schema to be set. + # Extraneous attribute names are ignored. + try: + credential_value = credential_values[attribute] + except KeyError: + raise AnonCredsRevocationError( + "Provided credential values are missing a value " + f"for the schema attribute '{attribute}'" + ) + + raw_values[attribute] = str(credential_value) + + if rev_reg_def_id and tails_file_path: + try: + async with self.profile.transaction() as txn: + rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) + rev_reg_def = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, rev_reg_def_id + ) + rev_key = await txn.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id + ) + if not rev_list: + raise AnonCredsRevocationError("Revocation registry not found") + if not rev_reg_def: + raise AnonCredsRevocationError( + "Revocation registry definition not found" + ) + if not rev_key: + raise AnonCredsRevocationError( + "Revocation registry definition private data not found" + ) + rev_info = rev_list.value_json + rev_info_tags = rev_list.tags + rev_reg_index = rev_info["next_index"] + try: + rev_reg_def = RevocationRegistryDefinition.load( + rev_reg_def.raw_value + ) + rev_list = RevocationStatusList.load(rev_info["rev_list"]) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error loading revocation registry definition" + ) from err + if rev_reg_index > rev_reg_def.max_cred_num: + raise AnonCredsRevocationRegistryFullError( + "Revocation registry is full" + ) + rev_info["next_index"] = rev_reg_index + 1 + await txn.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value_json=rev_info, + tags=rev_info_tags, + ) + await txn.commit() + except AskarError as err: + raise AnonCredsRevocationError( + "Error updating revocation registry index" + ) from err + + # rev_info["next_index"] is 1 based but getting from + # rev_list is zero based... + revoc = CredentialRevocationConfig( + rev_reg_def, + rev_key.raw_value, + rev_list, + rev_reg_index, + ) + credential_revocation_id = str(rev_reg_index) + else: + revoc = None + credential_revocation_id = None + rev_list = None + + try: + credential = await asyncio.get_event_loop().run_in_executor( + None, + lambda: W3cCredential.create( + cred_def=cred_def.raw_value, + cred_def_private=cred_def_private.raw_value, + cred_offer=credential_offer, + cred_request=credential_request, + attr_raw_values=raw_values, + revocation_config=revoc, + ), + ) + + except AnoncredsError as err: + raise AnonCredsRevocationError("Error creating credential") from err + + return credential.to_json(), credential_revocation_id + + async def create_credential_w3c( + self, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + *, + retries: int = 5, + ) -> Tuple[str, str, str]: + """Create a credential. + + Args: + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + revoc_reg_id: ID of the revocation registry + retries: number of times to retry credential creation + + Returns: + A tuple of created credential and revocation id + + """ + issuer = AnonCredsIssuer(self.profile) + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + schema_id = credential_offer["schema_id"] + schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) + cred_def_id = credential_offer["cred_def_id"] + schema_attributes = schema_result.schema_value.attr_names + + revocable = await issuer.cred_def_supports_revocation(cred_def_id) + + for attempt in range(max(retries, 1)): + if attempt > 0: + LOGGER.info( + "Retrying credential creation for revocation registry %s", + cred_def_id, + ) + await asyncio.sleep(2) + + rev_reg_def_result = None + if revocable: + rev_reg_def_result = await self.get_or_create_active_registry( + cred_def_id + ) + if ( + rev_reg_def_result.revocation_registry_definition_state.state + != STATE_FINISHED + ): + continue + rev_reg_def_id = rev_reg_def_result.rev_reg_def_id + tails_file_path = self.get_local_tails_path( + rev_reg_def_result.rev_reg_def + ) + else: + rev_reg_def_id = None + tails_file_path = None + + try: + cred_json, cred_rev_id = await self._create_credential_w3c( + cred_def_id, + schema_attributes, + credential_offer, + credential_request, + credential_values, + rev_reg_def_id, + tails_file_path, + ) + except AnonCredsRevocationRegistryFullError: + continue + + if rev_reg_def_result: + if ( + rev_reg_def_result.rev_reg_def.value.max_cred_num + <= int(cred_rev_id) + 1 + ): + await self.handle_full_registry(rev_reg_def_id) + return cred_json, cred_rev_id, rev_reg_def_id + + raise AnonCredsRevocationError("Failed to create credential after retrying") + async def _create_credential( self, credential_definition_id: str, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py index e2a688db96..9a38f5db9c 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py @@ -460,15 +460,30 @@ async def issue_credential( async with ledger: schema_id = await ledger.credential_definition_id2schema_id(cred_def_id) + cred_def = await ledger.get_credential_definition(cred_def_id) + revocable = cred_def["value"].get("revocation") + print("#5 revocable from vc_di: ", revocable) legacy_offer = await self._prepare_legacy_offer(cred_offer, schema_id) legacy_request = await self._prepare_legacy_request(cred_request, cred_def_id) - issuer = AnonCredsIssuer(self.profile) - - credential = await issuer.create_credential_w3c( - legacy_offer, legacy_request, cred_values - ) + cred_rev_id = None + rev_reg_def_id = None + credential = None + if revocable: + issuer = AnonCredsRevocation(self.profile) + ( + credential, + cred_rev_id, + rev_reg_def_id, + ) = await issuer.create_credential_w3c( + legacy_offer, legacy_request, cred_values + ) + else: + issuer = AnonCredsIssuer(self.profile) + credential = await issuer.create_credential_w3c( + legacy_offer, legacy_request, cred_values + ) vcdi_credential = { "credential": json.loads(credential), @@ -476,9 +491,6 @@ async def issue_credential( result = self.get_format_data(CRED_20_ISSUE, vcdi_credential) - cred_rev_id = None - rev_reg_def_id = None - async with self._profile.transaction() as txn: detail_record = V20CredExRecordIndy( cred_ex_id=cred_ex_record.cred_ex_id, From cff8b992bc690b258f93bfbf76309dc8edbfeb31 Mon Sep 17 00:00:00 2001 From: EmadAnwer Date: Sun, 2 Jun 2024 16:20:30 +0300 Subject: [PATCH 02/31] feat: Refactor revocation module and improve credential handling Signed-off-by: EmadAnwer --- aries_cloudagent/anoncreds/revocation.py | 69 +++++----- .../anoncreds/tests/test_revocation.py | 127 ++++++++++++++++++ .../v2_0/formats/vc_di/handler.py | 19 ++- 3 files changed, 173 insertions(+), 42 deletions(-) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 2eb1888fdd..8973ab0f91 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -39,7 +39,6 @@ CATEGORY_CRED_DEF_PRIVATE, STATE_FINISHED, AnonCredsIssuer, - AnonCredsIssuerError, ) from .models.anoncreds_revocation import ( RevList, @@ -719,8 +718,6 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef): backoff=-0.5, max_attempts=5, # heuristic: respect HTTP timeout ) - print("#1 tails_server", upload_success) - print("#2 tails_server", result) if not upload_success: raise AnonCredsRevocationError( @@ -899,29 +896,29 @@ async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResu async def _create_credential_w3c( self, - credential_definition_id: str, + w3c_credential_definition_id: str, schema_attributes: List[str], - credential_offer: dict, - credential_request: dict, - credential_values: dict, + w3c_credential_offer: dict, + w3c_credential_request: dict, + w3c_credential_values: dict, rev_reg_def_id: Optional[str] = None, tails_file_path: Optional[str] = None, ) -> Tuple[str, str]: try: async with self.profile.session() as session: cred_def = await session.handle.fetch( - CATEGORY_CRED_DEF, credential_definition_id + CATEGORY_CRED_DEF, w3c_credential_definition_id ) cred_def_private = await session.handle.fetch( - CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + CATEGORY_CRED_DEF_PRIVATE, w3c_credential_definition_id ) except AskarError as err: raise AnonCredsRevocationError( - "Error retrieving credential definition" + "Error retrieving w3c_credential definition" ) from err if not cred_def or not cred_def_private: raise AnonCredsRevocationError( - "Credential definition not found for credential issuance" + "Credential definition not found for w3c_credential issuance" ) raw_values = {} @@ -929,14 +926,14 @@ async def _create_credential_w3c( # Ensure every attribute present in schema to be set. # Extraneous attribute names are ignored. try: - credential_value = credential_values[attribute] + w3c_credential_value = w3c_credential_values[attribute] except KeyError: raise AnonCredsRevocationError( - "Provided credential values are missing a value " + "Provided w3c_credential values are missing a value " f"for the schema attribute '{attribute}'" ) - raw_values[attribute] = str(credential_value) + raw_values[attribute] = str(w3c_credential_value) if rev_reg_def_id and tails_file_path: try: @@ -995,20 +992,20 @@ async def _create_credential_w3c( rev_list, rev_reg_index, ) - credential_revocation_id = str(rev_reg_index) + w3c_credential_revocation_id = str(rev_reg_index) else: revoc = None - credential_revocation_id = None + w3c_credential_revocation_id = None rev_list = None try: - credential = await asyncio.get_event_loop().run_in_executor( + w3c_credential = await asyncio.get_event_loop().run_in_executor( None, lambda: W3cCredential.create( cred_def=cred_def.raw_value, cred_def_private=cred_def_private.raw_value, - cred_offer=credential_offer, - cred_request=credential_request, + cred_offer=w3c_credential_offer, + cred_request=w3c_credential_request, attr_raw_values=raw_values, revocation_config=revoc, ), @@ -1017,34 +1014,34 @@ async def _create_credential_w3c( except AnoncredsError as err: raise AnonCredsRevocationError("Error creating credential") from err - return credential.to_json(), credential_revocation_id + return w3c_credential.to_json(), w3c_credential_revocation_id async def create_credential_w3c( self, - credential_offer: dict, - credential_request: dict, - credential_values: dict, + w3c_credential_offer: dict, + w3c_credential_request: dict, + w3c_credential_values: dict, *, retries: int = 5, ) -> Tuple[str, str, str]: - """Create a credential. + """Create a w3c_credential. Args: - credential_offer: Credential Offer to create credential for - credential_request: Credential request to create credential for - credential_values: Values to go in credential + w3c_credential_offer: Credential Offer to create w3c_credential for + w3c_credential_request: Credential request to create w3c_credential for + w3c_credential_values: Values to go in w3c_credential revoc_reg_id: ID of the revocation registry - retries: number of times to retry credential creation + retries: number of times to retry w3c_credential creation Returns: - A tuple of created credential and revocation id + A tuple of created w3c_credential and revocation id """ issuer = AnonCredsIssuer(self.profile) anoncreds_registry = self.profile.inject(AnonCredsRegistry) - schema_id = credential_offer["schema_id"] + schema_id = w3c_credential_offer["schema_id"] schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) - cred_def_id = credential_offer["cred_def_id"] + cred_def_id = w3c_credential_offer["cred_def_id"] schema_attributes = schema_result.schema_value.attr_names revocable = await issuer.cred_def_supports_revocation(cred_def_id) @@ -1052,7 +1049,7 @@ async def create_credential_w3c( for attempt in range(max(retries, 1)): if attempt > 0: LOGGER.info( - "Retrying credential creation for revocation registry %s", + "Retrying w3c_credential creation for revocation registry %s", cred_def_id, ) await asyncio.sleep(2) @@ -1079,9 +1076,9 @@ async def create_credential_w3c( cred_json, cred_rev_id = await self._create_credential_w3c( cred_def_id, schema_attributes, - credential_offer, - credential_request, - credential_values, + w3c_credential_offer, + w3c_credential_request, + w3c_credential_values, rev_reg_def_id, tails_file_path, ) @@ -1096,7 +1093,7 @@ async def create_credential_w3c( await self.handle_full_registry(rev_reg_def_id) return cred_json, cred_rev_id, rev_reg_def_id - raise AnonCredsRevocationError("Failed to create credential after retrying") + raise AnonCredsRevocationError("Failed to create w3c_credential after retrying") async def _create_credential( self, diff --git a/aries_cloudagent/anoncreds/tests/test_revocation.py b/aries_cloudagent/anoncreds/tests/test_revocation.py index 26a75d57c2..064a0db5de 100644 --- a/aries_cloudagent/anoncreds/tests/test_revocation.py +++ b/aries_cloudagent/anoncreds/tests/test_revocation.py @@ -11,6 +11,7 @@ RevocationRegistryDefinitionPrivate, RevocationStatusList, Schema, + W3cCredential, ) from aries_askar import AskarError, AskarErrorCode from requests import RequestException, Session @@ -1380,3 +1381,129 @@ async def test_clear_pending_revocations_with_non_anoncreds_session(self): await self.revocation.clear_pending_revocations( self.profile.session(), rev_reg_def_id="test-rev-reg-id" ) + + @mock.patch.object( + AnonCredsIssuer, "cred_def_supports_revocation", return_value=True + ) + async def test_create_credential_w3c(self, mock_supports_revocation): + self.profile.inject = mock.Mock( + return_value=mock.MagicMock( + get_schema=mock.CoroutineMock( + return_value=GetSchemaResult( + schema_id="CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + schema=AnonCredsSchema( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + name="MYCO Biomarker:0.0.3", + version="1.0", + attr_names=["attr1", "attr2"], + ), + schema_metadata={}, + resolution_metadata={}, + ) + ) + ) + ) + self.revocation.get_or_create_active_registry = mock.CoroutineMock( + return_value=RevRegDefResult( + job_id="test-job-id", + revocation_registry_definition_state=RevRegDefState( + state=RevRegDefState.STATE_FINISHED, + revocation_registry_definition_id="active-reg-reg", + revocation_registry_definition=rev_reg_def, + ), + registration_metadata={}, + revocation_registry_definition_metadata={}, + ) + ) + + self.revocation._create_credential_w3c = mock.CoroutineMock( + return_value=({"cred": "cred"}, 98) + ) + + result = await self.revocation.create_credential_w3c( + w3c_credential_offer={ + "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + "key_correctness_proof": {}, + "nonce": "nonce", + }, + w3c_credential_request={}, + w3c_credential_values={}, + ) + + assert isinstance(result, tuple) + assert mock_supports_revocation.call_count == 1 + + @mock.patch.object(InMemoryProfileSession, "handle") + @mock.patch.object(W3cCredential, "create", return_value=mock.MagicMock()) + async def test_create_credential_w3c_private_no_rev_reg_or_tails( + self, mock_create, mock_handle + ): + mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), MockEntry()]) + await self.revocation._create_credential_w3c( + w3c_credential_definition_id="test-cred-def-id", + schema_attributes=["attr1", "attr2"], + w3c_credential_offer={ + "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + "key_correctness_proof": {}, + "nonce": "nonce", + }, + w3c_credential_request={}, + w3c_credential_values={ + "attr1": "value1", + "attr2": "value2", + }, + ) + assert mock_create.called + + # askar error retrieving cred def + mock_handle.fetch = mock.CoroutineMock( + side_effect=AskarError(AskarErrorCode.UNEXPECTED, "test") + ) + with self.assertRaises(test_module.AnonCredsRevocationError): + await self.revocation._create_credential_w3c( + w3c_credential_definition_id="test-cred-def-id", + schema_attributes=["attr1", "attr2"], + w3c_credential_offer={}, + w3c_credential_request={}, + w3c_credential_values={}, + ) + + # missing cred def or cred def private + mock_handle.fetch = mock.CoroutineMock(side_effect=[None, MockEntry()]) + with self.assertRaises(test_module.AnonCredsRevocationError): + await self.revocation._create_credential_w3c( + w3c_credential_definition_id="test-cred-def-id", + schema_attributes=["attr1", "attr2"], + w3c_credential_offer={}, + w3c_credential_request={}, + w3c_credential_values={}, + ) + mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None]) + with self.assertRaises(test_module.AnonCredsRevocationError): + await self.revocation._create_credential_w3c( + w3c_credential_definition_id="test-cred-def-id", + schema_attributes=["attr1", "attr2"], + w3c_credential_offer={}, + w3c_credential_request={}, + w3c_credential_values={}, + ) + + mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None]) + with self.assertRaises(test_module.AnonCredsRevocationError): + await self.revocation._create_credential_w3c( + w3c_credential_definition_id="test-cred-def-id", + schema_attributes=["attr1", "attr2"], + w3c_credential_offer={ + "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + "key_correctness_proof": {}, + "nonce": "nonce", + }, + w3c_credential_request={}, + w3c_credential_values={ + "x": "value1", + "y": "value2", + }, + ) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py index 9a38f5db9c..100b44530e 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py @@ -7,7 +7,7 @@ import json import logging from typing import Mapping, Tuple - +from anoncreds import W3cCredential from ...models.cred_ex_record import V20CredExRecord from ...models.detail.indy import ( V20CredExRecordIndy, @@ -462,7 +462,6 @@ async def issue_credential( schema_id = await ledger.credential_definition_id2schema_id(cred_def_id) cred_def = await ledger.get_credential_definition(cred_def_id) revocable = cred_def["value"].get("revocation") - print("#5 revocable from vc_di: ", revocable) legacy_offer = await self._prepare_legacy_offer(cred_offer, schema_id) legacy_request = await self._prepare_legacy_request(cred_request, cred_def_id) @@ -566,10 +565,18 @@ async def store_credential( cred_def_result = await anoncreds_registry.get_credential_definition( self.profile, cred["proof"][0]["verificationMethod"] ) - if cred["proof"][0].get("rev_reg_id"): + # TODO: remove loading of W3cCredential and use the credential directly + try: + cred_w3c = W3cCredential.load(cred) + rev_reg_id = cred_w3c.rev_reg_id + rev_reg_index = cred_w3c.rev_reg_index + except AnonCredsHolderError as e: + LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}") + raise e + if rev_reg_id: rev_reg_def_result = ( await anoncreds_registry.get_revocation_registry_definition( - self.profile, cred["proof"][0]["rev_reg_id"] + self.profile, rev_reg_id ) ) rev_reg_def = rev_reg_def_result.revocation_registry @@ -600,8 +607,8 @@ async def store_credential( ) detail_record.cred_id_stored = cred_id_stored - detail_record.rev_reg_id = cred["proof"][0].get("rev_reg_id", None) - detail_record.cred_rev_id = cred["proof"][0].get("cred_rev_id", None) + detail_record.rev_reg_id = rev_reg_id + detail_record.cred_rev_id = rev_reg_index async with self.profile.session() as session: # Store detail record, emit event From ecc0f4631a912016c200c43385d28ad2a2064b3e Mon Sep 17 00:00:00 2001 From: pstlouis Date: Mon, 3 Jun 2024 09:13:56 -0400 Subject: [PATCH 03/31] update demo dependencies Signed-off-by: pstlouis --- demo/playground/examples/poetry.lock | 246 ++++++++++++------------ demo/playground/examples/pyproject.toml | 6 +- demo/requirements.txt | 8 +- 3 files changed, 130 insertions(+), 130 deletions(-) diff --git a/demo/playground/examples/poetry.lock b/demo/playground/examples/poetry.lock index eb054b9b75..276a150f88 100644 --- a/demo/playground/examples/poetry.lock +++ b/demo/playground/examples/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "asynctest" @@ -13,112 +13,112 @@ files = [ [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] name = "charset-normalizer" -version = "3.3.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -134,13 +134,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -170,24 +170,24 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -196,13 +196,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -218,31 +218,31 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.21.1" +version = "0.23.7" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, - {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] -pytest = ">=7.0.0" +pytest = ">=7.0.0,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -268,22 +268,22 @@ files = [ [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "c9c1bb0fed9cb41eaa74390c7ba76d4e84a6fc5a0a849f770640a4dbcfb1fbca" +content-hash = "341ab30c68bf565ad15df85c008f204b909c4c60cf2aa0c46b2d42bd97287b29" diff --git a/demo/playground/examples/pyproject.toml b/demo/playground/examples/pyproject.toml index bfc662bbd4..fd4b622227 100644 --- a/demo/playground/examples/pyproject.toml +++ b/demo/playground/examples/pyproject.toml @@ -6,10 +6,10 @@ authors = ["Jason Sherman "] [tool.poetry.dependencies] python = "^3.9" -pytest = "^7.4.0" -pytest-asyncio = "^0.21.0" +pytest = "^7.4.4" +pytest-asyncio = "^0.23.7" asynctest = "^0.13.0" -requests = "^2.31.0" +requests = "^2.32.3" [tool.poetry.dev-dependencies] diff --git a/demo/requirements.txt b/demo/requirements.txt index 7bfc16bbfc..f95fae7726 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,5 +1,5 @@ -asyncpg~=0.26.0 -prompt_toolkit~=2.0.9 +asyncpg~=0.29.0 +prompt_toolkit~=3.0.45 web.py~=0.62 -pygments~=2.10 -qrcode[pil]~=6.1 +pygments~=2.18 +qrcode[pil]~=7.4 From 6a751850ed37c3eca5783f7325d50a724e93b764 Mon Sep 17 00:00:00 2001 From: pstlouis Date: Mon, 3 Jun 2024 09:53:25 -0400 Subject: [PATCH 04/31] revert prompt_toolkit version in demo/requirements.txt Signed-off-by: pstlouis --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index f95fae7726..e6013b0a46 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,5 +1,5 @@ asyncpg~=0.29.0 -prompt_toolkit~=3.0.45 +prompt_toolkit~=2.0.9 web.py~=0.62 pygments~=2.18 qrcode[pil]~=7.4 From e10c8b4ff0d4c52435c7df000e141e0b5898966f Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 May 2024 11:57:29 -0400 Subject: [PATCH 05/31] feat: soft binding for plugin flexibility Signed-off-by: Daniel Bluhm --- aries_cloudagent/askar/profile.py | 2 +- aries_cloudagent/config/injector.py | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index 27cec91b7c..b07dcf1f6b 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -114,7 +114,7 @@ def bind_providers(self): "aries_cloudagent.indy.credx.issuer.IndyCredxIssuer", ref(self) ), ) - injector.bind_provider( + injector.soft_bind_provider( VCHolder, ClassProvider( "aries_cloudagent.storage.vc_holder.askar.AskarVCHolder", diff --git a/aries_cloudagent/config/injector.py b/aries_cloudagent/config/injector.py index 9c47bd1ec5..26130623db 100644 --- a/aries_cloudagent/config/injector.py +++ b/aries_cloudagent/config/injector.py @@ -1,6 +1,6 @@ """Standard Injector implementation.""" -from typing import Mapping, Optional, Type +from typing import Dict, Mapping, Optional, Type from .base import BaseProvider, BaseInjector, InjectionError, InjectType from .provider import InstanceProvider, CachedProvider @@ -18,7 +18,7 @@ def __init__( ): """Initialize an `Injector`.""" self.enforce_typing = enforce_typing - self._providers = {} + self._providers: Dict[Type, BaseProvider] = {} self._settings = Settings(settings) @property @@ -45,6 +45,24 @@ def bind_provider( provider = CachedProvider(provider) self._providers[base_cls] = provider + def soft_bind_instance(self, base_cls: Type[InjectType], instance: InjectType): + """Add a static instance as a soft class binding. + + The binding occurs only if a provider for the same type does not already exist. + """ + if not self.get_provider(base_cls): + self.bind_instance(base_cls, instance) + + def soft_bind_provider( + self, base_cls: Type[InjectType], provider: BaseProvider, *, cache: bool = False + ): + """Add a dynamic instance resolver as a soft class binding. + + The binding occurs only if a provider for the same type does not already exist. + """ + if not self.get_provider(base_cls): + self.bind_provider(base_cls, provider, cache=cache) + def clear_binding(self, base_cls: Type[InjectType]): """Remove a previously-added binding.""" if base_cls in self._providers: From 6c4f6f4084b119a8790bdc77705d3be8995d79e9 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 3 Jun 2024 11:21:51 -0400 Subject: [PATCH 06/31] test: soft binding Signed-off-by: Daniel Bluhm --- .../config/tests/test_injector.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/aries_cloudagent/config/tests/test_injector.py b/aries_cloudagent/config/tests/test_injector.py index 76da5f7992..3b5023307f 100644 --- a/aries_cloudagent/config/tests/test_injector.py +++ b/aries_cloudagent/config/tests/test_injector.py @@ -70,6 +70,39 @@ def test_inject_provider(self): assert mock_provider.settings[self.test_key] == override_settings[self.test_key] assert mock_provider.injector is self.test_instance + def test_inject_soft_provider_bindings(self): + """Test injecting providers with soft binding.""" + provider = MockProvider(self.test_value) + override = MockProvider("Override") + + self.test_instance.soft_bind_provider(str, provider) + assert self.test_instance.inject(str) == self.test_value + + self.test_instance.clear_binding(str) + # Bound by a plugin on startup, for example + self.test_instance.bind_provider(str, override) + + # Bound later in Profile.bind_providerse + self.test_instance.soft_bind_provider(str, provider) + + # We want the plugin value, not the Profile bound value + assert self.test_instance.inject(str) == "Override" + + def test_inject_soft_instance_bindings(self): + """Test injecting providers with soft binding.""" + self.test_instance.soft_bind_instance(str, self.test_value) + assert self.test_instance.inject(str) == self.test_value + + self.test_instance.clear_binding(str) + # Bound by a plugin on startup, for example + self.test_instance.bind_instance(str, "Override") + + # Bound later in Profile.bind_providerse + self.test_instance.soft_bind_instance(str, self.test_value) + + # We want the plugin value, not the Profile bound value + assert self.test_instance.inject(str) == "Override" + def test_bad_provider(self): """Test empty and invalid provider results.""" self.test_instance.bind_provider(str, MockProvider(None)) From 3442c07900ee35de04f8d8d3634d5e958ff0e5e8 Mon Sep 17 00:00:00 2001 From: Ian Costanzo Date: Mon, 3 Jun 2024 12:17:49 -0700 Subject: [PATCH 07/31] Integration tests for vc_di cred revocation Signed-off-by: Ian Costanzo --- demo/features/0453-issue-credential.feature | 7 ++-- demo/features/0454-present-proof.feature | 39 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/demo/features/0453-issue-credential.feature b/demo/features/0453-issue-credential.feature index fbe1c49c07..a6736295b7 100644 --- a/demo/features/0453-issue-credential.feature +++ b/demo/features/0453-issue-credential.feature @@ -64,9 +64,10 @@ Feature: RFC 0453 Aries agent issue credential @WalletType_Askar_AnonCreds @SwitchCredTypeTest Examples: - | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | - | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | vc_di | - | --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | indy | + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | + | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | vc_di | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | indy | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | indy | @T003-RFC0453 Scenario Outline: Holder accepts a deleted credential offer diff --git a/demo/features/0454-present-proof.feature b/demo/features/0454-present-proof.feature index f40552e003..bdbc2155c4 100644 --- a/demo/features/0454-present-proof.feature +++ b/demo/features/0454-present-proof.feature @@ -303,3 +303,42 @@ Feature: RFC 0454 Aries agent present proof Examples: | issuer1 | Acme1_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Proof_request | | Acme1 | --revocation --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + + @T003-RFC0454.5 + Scenario Outline: Present Proof for a vc_di-issued credential using "legacy" indy proof and the proof validates + Given we have "2" agents + | name | role | capabilities | extra | + | Acme | issuer | | | + | Bob | holder | | | + And "Acme" and "Bob" have an existing connection + And "Acme" is ready to issue a credential for + When "Acme" offers a credential with data + When "Bob" has the credential issued + When "Acme" sets the credential type to + When "Acme" sends a request with explicit revocation status for proof presentation to "Bob" + Then "Acme" has the proof verified + + @WalletType_Askar_AnonCreds @SwitchCredTypeTest + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | Proof_request | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | | | indy | DL_age_over_19_v2 | + + @T003-RFC0454.6 + Scenario Outline: Present Proof for a vc_di-issued credential using "legacy" indy proof and credential is revoked and the proof fails + Given we have "2" agents + | name | role | capabilities | extra | + | Acme | issuer | | | + | Bob | holder | | | + And "Acme" and "Bob" have an existing connection + And "Acme" is ready to issue a credential for + When "Acme" offers a credential with data + When "Bob" has the credential issued + When "Acme" sets the credential type to + And "Acme" revokes the credential + When "Acme" sends a request for proof presentation to "Bob" + Then "Acme" has the proof verification fail + + @WalletType_Askar_AnonCreds @SwitchCredTypeTest + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | Proof_request | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | | | indy | DL_age_over_19_v2 | From d232e6014d0c163b65b90ba2fd0d8c5adbac9052 Mon Sep 17 00:00:00 2001 From: jamshale <31809382+jamshale@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:54:52 -0700 Subject: [PATCH 08/31] Switch from pytz to dateutil (#3012) * Switch from pytz to dateutil Signed-off-by: jamshale --------- Signed-off-by: jamshale --- .../present_proof/dif/pres_exch_handler.py | 7 +-- .../suites/bbs_bls_signature_2020.py | 11 ++-- .../ld_proofs/suites/linked_data_signature.py | 8 +-- .../vc/vc_ld/models/credential.py | 11 ++-- docs/conf.py | 59 +++++++++---------- poetry.lock | 16 +---- pyproject.toml | 1 - 7 files changed, 49 insertions(+), 64 deletions(-) diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py index 8ccc271fd9..d46415abee 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py @@ -13,7 +13,7 @@ from datetime import datetime from typing import Dict, List, Optional, Sequence, Tuple, Union -import pytz +from dateutil import tz from dateutil.parser import ParserError from dateutil.parser import parse as dateutil_parser from jsonpath_ng import parse @@ -613,10 +613,9 @@ def string_to_timezone_aware_datetime(self, datetime_str: str) -> datetime: if PYTZ_TIMEZONE_PATTERN.search(datetime_str): result = PYTZ_TIMEZONE_PATTERN.search(datetime_str).group(1) datetime_str = datetime_str.replace(result, "") - return dateutil_parser(datetime_str).replace(tzinfo=pytz.timezone(result)) + return dateutil_parser(datetime_str).replace(tzinfo=tz.gettz(result)) else: - utc = pytz.UTC - return dateutil_parser(datetime_str).replace(tzinfo=utc) + return dateutil_parser(datetime_str).replace(tzinfo=tz.UTC) def validate_patch(self, to_check: any, _filter: Filter) -> bool: """Apply filter on match_value. diff --git a/aries_cloudagent/vc/ld_proofs/suites/bbs_bls_signature_2020.py b/aries_cloudagent/vc/ld_proofs/suites/bbs_bls_signature_2020.py index 915497c67e..585b1cc970 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/bbs_bls_signature_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/bbs_bls_signature_2020.py @@ -1,17 +1,16 @@ """BbsBlsSignature2020 class.""" -from datetime import datetime, timezone -from pytz import utc +from datetime import datetime from typing import List, Optional, Union -from ....wallet.util import b64_to_bytes, bytes_to_b64 +from dateutil import tz +from ....wallet.util import b64_to_bytes, bytes_to_b64 from ..crypto import _KeyPair as KeyPair from ..document_loader import DocumentLoaderMethod from ..error import LinkedDataProofException from ..purposes import _ProofPurpose as ProofPurpose from ..validation_result import ProofResult - from .bbs_bls_signature_2020_base import BbsBlsSignature2020Base @@ -60,9 +59,9 @@ async def create_proof( # Set created if not already set if not proof.get("created"): # Use class date, or now - date = self.date or datetime.now(timezone.utc) + date = self.date or datetime.now(tz.UTC) if not date.tzinfo: - date = utc.localize(date) + date = date.replace(tzinfo=tz.UTC) proof["created"] = date.isoformat(timespec="seconds") # Allow purpose to update the proof; the `proof` is in the diff --git a/aries_cloudagent/vc/ld_proofs/suites/linked_data_signature.py b/aries_cloudagent/vc/ld_proofs/suites/linked_data_signature.py index 36721f562e..d2a56a514c 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/linked_data_signature.py +++ b/aries_cloudagent/vc/ld_proofs/suites/linked_data_signature.py @@ -1,17 +1,17 @@ """Linked Data Signature class.""" -from abc import abstractmethod, ABCMeta +from abc import ABCMeta, abstractmethod from datetime import datetime, timezone from hashlib import sha256 -from pytz import utc from typing import Optional, Union +from dateutil import tz + from ..constants import SECURITY_CONTEXT_URL from ..document_loader import DocumentLoaderMethod from ..error import LinkedDataProofException from ..purposes import _ProofPurpose as ProofPurpose from ..validation_result import ProofResult - from .linked_data_proof import LinkedDataProof @@ -99,7 +99,7 @@ async def create_proof( # Use class date, or now date = self.date or datetime.now(timezone.utc) if not date.tzinfo: - date = utc.localize(date) + date = date.replace(tzinfo=tz.UTC) proof["created"] = date.isoformat(timespec="seconds") # Allow purpose to update the proof; the `proof` is in the diff --git a/aries_cloudagent/vc/vc_ld/models/credential.py b/aries_cloudagent/vc/vc_ld/models/credential.py index a436f9c84b..8ba27a9a4b 100644 --- a/aries_cloudagent/vc/vc_ld/models/credential.py +++ b/aries_cloudagent/vc/vc_ld/models/credential.py @@ -3,18 +3,17 @@ from datetime import datetime from typing import List, Optional, Union -from pytz import utc - +from dateutil import tz from marshmallow import INCLUDE, ValidationError, fields, post_dump from ....messaging.models.base import BaseModel, BaseModelSchema from ....messaging.valid import ( CREDENTIAL_CONTEXT_EXAMPLE, CREDENTIAL_CONTEXT_VALIDATE, - CREDENTIAL_SUBJECT_EXAMPLE, - CREDENTIAL_SUBJECT_VALIDATE, CREDENTIAL_STATUS_EXAMPLE, CREDENTIAL_STATUS_VALIDATE, + CREDENTIAL_SUBJECT_EXAMPLE, + CREDENTIAL_SUBJECT_VALIDATE, CREDENTIAL_TYPE_EXAMPLE, CREDENTIAL_TYPE_VALIDATE, RFC3339_DATETIME_EXAMPLE, @@ -176,7 +175,7 @@ def issuance_date(self, date: Union[str, datetime]): """Setter for issuance date.""" if isinstance(date, datetime): if not date.tzinfo: - date = utc.localize(date) + date = date.replace(tzinfo=tz.UTC) date = date.isoformat() self._issuance_date = date @@ -191,7 +190,7 @@ def expiration_date(self, date: Union[str, datetime, None]): """Setter for expiration date.""" if isinstance(date, datetime): if not date.tzinfo: - date = utc.localize(date) + date = date.replace(tzinfo=tz.UTC) date = date.isoformat() self._expiration_date = date diff --git a/docs/conf.py b/docs/conf.py index 336d7e620d..c07343c1a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,48 +18,47 @@ sys.path.insert(0, os.path.abspath("..")) autodoc_mock_imports = [ - "setup", - "nacl", - "indy", + "aiohttp_apispec", "aiohttp_cors", "aiohttp", - "aiohttp_apispec", - "base58", - "msgpack", - "pytest", + "aioredis", + "anoncreds", + "aries_askar", + "async_timeout", "asynctest", - "markdown", - "prompt_toolkit", - "multicodec", + "base58", "configargparse", - "pyld", - "pydid", - "aries_askar", - "indy_vdr", - "aioredis", + "dateutil", "deepmerge", + "did_peer_2", + "did_peer_4", "ecdsa", "indy_credx", - "dateutil", - "packaging", + "indy_vdr", + "indy", "jsonpath_ng", - "unflatten", - "qrcode", - "rlp", - "nest_asyncio", + "jwt", + "markdown", "marshmallow", - "typing_extensions", - "async_timeout", + "msgpack", + "multicodec", + "multiformats", + "nacl", + "nest_asyncio", + "packaging", "portalocker", + "prompt_toolkit", + "pydid", + "pyld", + "pytest", "pythonjsonlogger", - "jwt", - "yaml", - "pytz", - "multiformats", + "qrcode", + "rlp", "sd_jwt", - "anoncreds", - "did_peer_2", - "did_peer_4", + "setup", + "typing_extensions", + "unflatten", + "yaml", ] # "aries_cloudagent.tests.test_conductor", diff --git a/poetry.lock b/poetry.lock index 9a829c3316..750938c778 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2237,17 +2237,6 @@ files = [ {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, ] -[[package]] -name = "pytz" -version = "2021.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, -] - [[package]] name = "pywin32" version = "306" @@ -2296,6 +2285,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2895,4 +2885,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "ebf6f9d048937a52695115bf2aa1b2f71521116ccb5d9429f209aee041b91446" +content-hash = "7dbaa13873082c3ccdfdf47d5381b85d30c7c64800884148095e344955f3e27c" diff --git a/pyproject.toml b/pyproject.toml index ec910e97ed..e5b45a51f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ pyld="^2.0.4" pynacl="~1.5.0" python-dateutil="~2.8.1" python-json-logger="~2.0.7" -pytz="~2021.1" pyyaml="~6.0.1" qrcode = {version = ">=6.1,<7.0", extras = ["pil"]} requests="~2.31.0" From 03efc317b25c145e216b90da853d391973ba6243 Mon Sep 17 00:00:00 2001 From: jamshale <31809382+jamshale@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:46:07 -0700 Subject: [PATCH 09/31] Add sonarcloud badges (#3014) Signed-off-by: jamshale --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d25d2b5814..b736900c6d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # Hyperledger Aries Cloud Agent - Python -[![pypi releases](https://img.shields.io/pypi/v/aries_cloudagent)](https://pypi.org/project/aries-cloudagent/) - - +

+ + + +   +   +   +

> An easy to use Aries agent for building SSI services using any language that supports sending/receiving HTTP requests. From 4c49252a0730b580cd2044bf2251832a1982843c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:15:09 -0700 Subject: [PATCH 10/31] chore(deps): Bump actions/checkout from 3 to 4 in the all-actions group (#3011) Bumps the all-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 3 to 4 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: jamshale <31809382+jamshale@users.noreply.github.com> --- .github/workflows/sonar-merge-main.yml | 2 +- .github/workflows/sonar-pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar-merge-main.yml b/.github/workflows/sonar-merge-main.yml index 9f00bc5f59..0962261203 100644 --- a/.github/workflows/sonar-merge-main.yml +++ b/.github/workflows/sonar-merge-main.yml @@ -9,7 +9,7 @@ jobs: name: SonarCloud runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Tests diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml index a88ab3e060..5f57b2d750 100644 --- a/.github/workflows/sonar-pr.yml +++ b/.github/workflows/sonar-pr.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download PR number artifact From 1da987291d36e8386e035f123d7015b154b3193f Mon Sep 17 00:00:00 2001 From: jamshale <31809382+jamshale@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:45:27 -0700 Subject: [PATCH 11/31] Use a published version of aiohttp-apispec (#3019) * Use a published version of aiohttp-apispec Signed-off-by: jamshale * fix lock file Signed-off-by: jamshale * Update version of aiohttp-apispec-acapy Signed-off-by: jamshale --------- Signed-off-by: jamshale --- poetry.lock | 18 +++++++----------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 750938c778..47596a3f81 100644 --- a/poetry.lock +++ b/poetry.lock @@ -97,13 +97,15 @@ yarl = ">=1.0,<2.0" speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] -name = "aiohttp-apispec" -version = "3.0.1" +name = "aiohttp-apispec-acapy" +version = "3.0.2" description = "Build and document REST APIs with aiohttp and apispec" optional = false python-versions = ">=3.9" -files = [] -develop = false +files = [ + {file = "aiohttp-apispec-acapy-3.0.2.tar.gz", hash = "sha256:9e6946a48cb70d3f7097f51e2ce7ba8bee32fce9d654454fe300930bfa8ce542"}, + {file = "aiohttp_apispec_acapy-3.0.2-py3-none-any.whl", hash = "sha256:93ea532afb3876685d185cc1cfe51d6d08e597cf04f79d16898a23ac4842b742"}, +] [package.dependencies] aiohttp = ">=3.9.4,<4.0" @@ -111,12 +113,6 @@ apispec = ">=6.6.1,<6.7.0" jinja2 = ">=3.1.3,<3.2.0" webargs = ">=8.4.0,<8.5.0" -[package.source] -type = "git" -url = "https://github.com/ff137/aiohttp-apispec.git" -reference = "v3.0.1" -resolved_reference = "a2b67c0d26f6d3bf0234a8cd134f699144239eeb" - [[package]] name = "aiohttp-cors" version = "0.7.0" @@ -2885,4 +2881,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "7dbaa13873082c3ccdfdf47d5381b85d30c7c64800884148095e344955f3e27c" +content-hash = "5ec524034c85a7d2497fb43981a95e7d7e3cc356c2ada95f4e943c98899b07cb" diff --git a/pyproject.toml b/pyproject.toml index e5b45a51f3..558a6087b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ repository = "https://github.com/hyperledger/aries-cloudagent-python" [tool.poetry.dependencies] python = "^3.9" aiohttp="~3.9.4" -aiohttp-apispec = { git = "https://github.com/ff137/aiohttp-apispec.git", tag = "v3.0.1" } +aiohttp-apispec-acapy="~3.0.2" aiohttp-cors="~0.7.0" apispec="^6.6.0" async-timeout="~4.0.2" From aff4b188407ad110e583862a6143645f356d0015 Mon Sep 17 00:00:00 2001 From: EmadAnwer Date: Tue, 4 Jun 2024 21:55:57 +0300 Subject: [PATCH 12/31] refactor: remove duplicated code Signed-off-by: EmadAnwer --- aries_cloudagent/anoncreds/revocation.py | 252 +++++------------- .../anoncreds/tests/test_revocation.py | 91 ++----- 2 files changed, 89 insertions(+), 254 deletions(-) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 8973ab0f91..84a03ef866 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -8,7 +8,7 @@ import os import time from pathlib import Path -from typing import List, NamedTuple, Optional, Sequence, Tuple +from typing import List, NamedTuple, Optional, Sequence, Tuple, Union from urllib.parse import urlparse import base58 @@ -894,128 +894,6 @@ async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResu # Credential Operations - async def _create_credential_w3c( - self, - w3c_credential_definition_id: str, - schema_attributes: List[str], - w3c_credential_offer: dict, - w3c_credential_request: dict, - w3c_credential_values: dict, - rev_reg_def_id: Optional[str] = None, - tails_file_path: Optional[str] = None, - ) -> Tuple[str, str]: - try: - async with self.profile.session() as session: - cred_def = await session.handle.fetch( - CATEGORY_CRED_DEF, w3c_credential_definition_id - ) - cred_def_private = await session.handle.fetch( - CATEGORY_CRED_DEF_PRIVATE, w3c_credential_definition_id - ) - except AskarError as err: - raise AnonCredsRevocationError( - "Error retrieving w3c_credential definition" - ) from err - if not cred_def or not cred_def_private: - raise AnonCredsRevocationError( - "Credential definition not found for w3c_credential issuance" - ) - - raw_values = {} - for attribute in schema_attributes: - # Ensure every attribute present in schema to be set. - # Extraneous attribute names are ignored. - try: - w3c_credential_value = w3c_credential_values[attribute] - except KeyError: - raise AnonCredsRevocationError( - "Provided w3c_credential values are missing a value " - f"for the schema attribute '{attribute}'" - ) - - raw_values[attribute] = str(w3c_credential_value) - - if rev_reg_def_id and tails_file_path: - try: - async with self.profile.transaction() as txn: - rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) - rev_reg_def = await txn.handle.fetch( - CATEGORY_REV_REG_DEF, rev_reg_def_id - ) - rev_key = await txn.handle.fetch( - CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id - ) - if not rev_list: - raise AnonCredsRevocationError("Revocation registry not found") - if not rev_reg_def: - raise AnonCredsRevocationError( - "Revocation registry definition not found" - ) - if not rev_key: - raise AnonCredsRevocationError( - "Revocation registry definition private data not found" - ) - rev_info = rev_list.value_json - rev_info_tags = rev_list.tags - rev_reg_index = rev_info["next_index"] - try: - rev_reg_def = RevocationRegistryDefinition.load( - rev_reg_def.raw_value - ) - rev_list = RevocationStatusList.load(rev_info["rev_list"]) - except AnoncredsError as err: - raise AnonCredsRevocationError( - "Error loading revocation registry definition" - ) from err - if rev_reg_index > rev_reg_def.max_cred_num: - raise AnonCredsRevocationRegistryFullError( - "Revocation registry is full" - ) - rev_info["next_index"] = rev_reg_index + 1 - await txn.handle.replace( - CATEGORY_REV_LIST, - rev_reg_def_id, - value_json=rev_info, - tags=rev_info_tags, - ) - await txn.commit() - except AskarError as err: - raise AnonCredsRevocationError( - "Error updating revocation registry index" - ) from err - - # rev_info["next_index"] is 1 based but getting from - # rev_list is zero based... - revoc = CredentialRevocationConfig( - rev_reg_def, - rev_key.raw_value, - rev_list, - rev_reg_index, - ) - w3c_credential_revocation_id = str(rev_reg_index) - else: - revoc = None - w3c_credential_revocation_id = None - rev_list = None - - try: - w3c_credential = await asyncio.get_event_loop().run_in_executor( - None, - lambda: W3cCredential.create( - cred_def=cred_def.raw_value, - cred_def_private=cred_def_private.raw_value, - cred_offer=w3c_credential_offer, - cred_request=w3c_credential_request, - attr_raw_values=raw_values, - revocation_config=revoc, - ), - ) - - except AnoncredsError as err: - raise AnonCredsRevocationError("Error creating credential") from err - - return w3c_credential.to_json(), w3c_credential_revocation_id - async def create_credential_w3c( self, w3c_credential_offer: dict, @@ -1030,70 +908,19 @@ async def create_credential_w3c( w3c_credential_offer: Credential Offer to create w3c_credential for w3c_credential_request: Credential request to create w3c_credential for w3c_credential_values: Values to go in w3c_credential - revoc_reg_id: ID of the revocation registry retries: number of times to retry w3c_credential creation Returns: A tuple of created w3c_credential and revocation id """ - issuer = AnonCredsIssuer(self.profile) - anoncreds_registry = self.profile.inject(AnonCredsRegistry) - schema_id = w3c_credential_offer["schema_id"] - schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) - cred_def_id = w3c_credential_offer["cred_def_id"] - schema_attributes = schema_result.schema_value.attr_names - - revocable = await issuer.cred_def_supports_revocation(cred_def_id) - - for attempt in range(max(retries, 1)): - if attempt > 0: - LOGGER.info( - "Retrying w3c_credential creation for revocation registry %s", - cred_def_id, - ) - await asyncio.sleep(2) - - rev_reg_def_result = None - if revocable: - rev_reg_def_result = await self.get_or_create_active_registry( - cred_def_id - ) - if ( - rev_reg_def_result.revocation_registry_definition_state.state - != STATE_FINISHED - ): - continue - rev_reg_def_id = rev_reg_def_result.rev_reg_def_id - tails_file_path = self.get_local_tails_path( - rev_reg_def_result.rev_reg_def - ) - else: - rev_reg_def_id = None - tails_file_path = None - - try: - cred_json, cred_rev_id = await self._create_credential_w3c( - cred_def_id, - schema_attributes, - w3c_credential_offer, - w3c_credential_request, - w3c_credential_values, - rev_reg_def_id, - tails_file_path, - ) - except AnonCredsRevocationRegistryFullError: - continue - - if rev_reg_def_result: - if ( - rev_reg_def_result.rev_reg_def.value.max_cred_num - <= int(cred_rev_id) + 1 - ): - await self.handle_full_registry(rev_reg_def_id) - return cred_json, cred_rev_id, rev_reg_def_id - - raise AnonCredsRevocationError("Failed to create w3c_credential after retrying") + return await self._create_credential_helper( + w3c_credential_offer, + w3c_credential_request, + w3c_credential_values, + W3cCredential, + retries=retries, + ) async def _create_credential( self, @@ -1102,9 +929,26 @@ async def _create_credential( credential_offer: dict, credential_request: dict, credential_values: dict, + credential_type: Union[Credential, W3cCredential], rev_reg_def_id: Optional[str] = None, tails_file_path: Optional[str] = None, ) -> Tuple[str, str]: + """Create a credential. + + Args: + credential_definition_id: The credential definition ID + schema_attributes: The schema attributes + credential_offer: The credential offer + credential_request: The credential request + credential_values: The credential values + credential_type: The credential type + rev_reg_def_id: The revocation registry definition ID + tails_file_path: The tails file path + + Returns: + A tuple of created credential and revocation ID + + """ try: async with self.profile.session() as session: cred_def = await session.handle.fetch( @@ -1207,14 +1051,13 @@ async def _create_credential( try: credential = await asyncio.get_event_loop().run_in_executor( None, - lambda: Credential.create( - cred_def.raw_value, - cred_def_private.raw_value, - credential_offer, - credential_request, - raw_values, - None, - revoc, + lambda: credential_type.create( + cred_def=cred_def.raw_value, + cred_def_private=cred_def_private.raw_value, + cred_offer=credential_offer, + cred_request=credential_request, + attr_raw_values=raw_values, + revocation_config=revoc, ), ) except AnoncredsError as err: @@ -1242,6 +1085,36 @@ async def create_credential( Returns: A tuple of created credential and revocation id + """ + return await self._create_credential_helper( + credential_offer, + credential_request, + credential_values, + Credential, + retries=retries, + ) + + async def _create_credential_helper( + self, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + credential_type: Union[Credential, W3cCredential], + *, + retries: int = 5, + ) -> Tuple[str, str, str]: + """Create a credential. + + Args: + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + credential_type: Credential or W3cCredential + retries: number of times to retry credential creation + + Returns: + A tuple of created credential and revocation id + """ issuer = AnonCredsIssuer(self.profile) anoncreds_registry = self.profile.inject(AnonCredsRegistry) @@ -1284,6 +1157,7 @@ async def create_credential( credential_offer, credential_request, credential_values, + credential_type, rev_reg_def_id, tails_file_path, ) diff --git a/aries_cloudagent/anoncreds/tests/test_revocation.py b/aries_cloudagent/anoncreds/tests/test_revocation.py index 064a0db5de..f4a3a12ad6 100644 --- a/aries_cloudagent/anoncreds/tests/test_revocation.py +++ b/aries_cloudagent/anoncreds/tests/test_revocation.py @@ -11,7 +11,9 @@ RevocationRegistryDefinitionPrivate, RevocationStatusList, Schema, - W3cCredential, + # AnoncredsError, + # W3cCredential, + # CredentialRevocationConfig, ) from aries_askar import AskarError, AskarErrorCode from requests import RequestException, Session @@ -1025,6 +1027,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( "attr1": "value1", "attr2": "value2", }, + credential_type=Credential, ) assert mock_create.called @@ -1039,6 +1042,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( credential_offer={}, credential_request={}, credential_values={}, + credential_type=Credential, ) # missing cred def or cred def private @@ -1050,6 +1054,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( credential_offer={}, credential_request={}, credential_values={}, + credential_type=Credential, ) mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None]) with self.assertRaises(test_module.AnonCredsRevocationError): @@ -1059,6 +1064,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( credential_offer={}, credential_request={}, credential_values={}, + credential_type=Credential, ) @mock.patch.object(InMemoryProfileSession, "handle") @@ -1087,6 +1093,7 @@ async def call_test_func(): }, rev_reg_def_id="test-rev-reg-def-id", tails_file_path="tails-file-path", + credential_type=Credential, ) # missing rev list @@ -1416,7 +1423,8 @@ async def test_create_credential_w3c(self, mock_supports_revocation): ) ) - self.revocation._create_credential_w3c = mock.CoroutineMock( + # Test private funtion seperately - very large + self.revocation._create_credential = mock.CoroutineMock( return_value=({"cred": "cred"}, 98) ) @@ -1434,76 +1442,29 @@ async def test_create_credential_w3c(self, mock_supports_revocation): assert isinstance(result, tuple) assert mock_supports_revocation.call_count == 1 + @pytest.mark.asyncio @mock.patch.object(InMemoryProfileSession, "handle") - @mock.patch.object(W3cCredential, "create", return_value=mock.MagicMock()) - async def test_create_credential_w3c_private_no_rev_reg_or_tails( - self, mock_create, mock_handle - ): + async def test_create_credential_w3c_keyerror(self, mock_handle): mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), MockEntry()]) - await self.revocation._create_credential_w3c( - w3c_credential_definition_id="test-cred-def-id", - schema_attributes=["attr1", "attr2"], - w3c_credential_offer={ - "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", - "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", - "key_correctness_proof": {}, - "nonce": "nonce", - }, - w3c_credential_request={}, - w3c_credential_values={ - "attr1": "value1", - "attr2": "value2", - }, - ) - assert mock_create.called - - # askar error retrieving cred def - mock_handle.fetch = mock.CoroutineMock( - side_effect=AskarError(AskarErrorCode.UNEXPECTED, "test") - ) - with self.assertRaises(test_module.AnonCredsRevocationError): - await self.revocation._create_credential_w3c( - w3c_credential_definition_id="test-cred-def-id", - schema_attributes=["attr1", "attr2"], - w3c_credential_offer={}, - w3c_credential_request={}, - w3c_credential_values={}, - ) - - # missing cred def or cred def private - mock_handle.fetch = mock.CoroutineMock(side_effect=[None, MockEntry()]) - with self.assertRaises(test_module.AnonCredsRevocationError): - await self.revocation._create_credential_w3c( - w3c_credential_definition_id="test-cred-def-id", - schema_attributes=["attr1", "attr2"], - w3c_credential_offer={}, - w3c_credential_request={}, - w3c_credential_values={}, - ) - mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None]) - with self.assertRaises(test_module.AnonCredsRevocationError): - await self.revocation._create_credential_w3c( - w3c_credential_definition_id="test-cred-def-id", - schema_attributes=["attr1", "attr2"], - w3c_credential_offer={}, - w3c_credential_request={}, - w3c_credential_values={}, - ) - - mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None]) - with self.assertRaises(test_module.AnonCredsRevocationError): - await self.revocation._create_credential_w3c( - w3c_credential_definition_id="test-cred-def-id", + with pytest.raises(test_module.AnonCredsRevocationError) as excinfo: + await self.revocation._create_credential( + credential_definition_id="test-cred-def-id", schema_attributes=["attr1", "attr2"], - w3c_credential_offer={ + credential_offer={ "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", "key_correctness_proof": {}, "nonce": "nonce", }, - w3c_credential_request={}, - w3c_credential_values={ - "x": "value1", - "y": "value2", + credential_request={}, + credential_values={ + "X": "value1", + "Y": "value2", }, + credential_type=Credential, ) + + assert str(excinfo.value) == ( + "Provided credential values are missing a value " + "for the schema attribute 'attr1'" + ) From 630830e9b468012011b59bab4074a3bb6b18b9f9 Mon Sep 17 00:00:00 2001 From: EmadAnwer Date: Sun, 9 Jun 2024 17:41:10 +0300 Subject: [PATCH 13/31] ADD: tests for VCDI handler - test_create_offer - test_create_request - test_issue_credential_revocable - test_match_sent_cred_def_id_error - test_store_credential Signed-off-by: EmadAnwer --- .../v2_0/formats/vc_di/tests/test_handler.py | 375 +++++++++++++----- 1 file changed, 283 insertions(+), 92 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py index 04d3c552ef..02a99804b2 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py @@ -2,8 +2,25 @@ from time import time import json +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( + CredDef, + GetCredDefResult, +) +from aries_cloudagent.anoncreds.models.anoncreds_revocation import ( + GetRevRegDefResult, + RevRegDef, +) +from aries_cloudagent.anoncreds.registry import AnonCredsRegistry +from aries_cloudagent.askar.profile_anon import AskarAnoncredsProfile +from aries_cloudagent.protocols.issue_credential.v2_0.messages.cred_issue import ( + V20CredIssue, +) +from aries_cloudagent.protocols.issue_credential.v2_0.models.cred_ex_record import ( + V20CredExRecord, +) +from aries_cloudagent.wallet.did_info import DIDInfo import pytest -from .......anoncreds.holder import AnonCredsHolder +from .......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from .......messaging.credential_definitions.util import ( CRED_DEF_SENT_RECORD_TYPE, ) @@ -176,7 +193,8 @@ "data_model_version": "2.0", "binding_proof": { "anoncreds_link_secret": { - "entropy": "M7PyEDW7WfLDA8UH4BPhVN", + "prover_did": f"did:sov:{TEST_DID}", + "entropy": f"did:sov:{TEST_DID}", "cred_def_id": CRED_DEF_ID, "blinded_ms": { "u": "10047077609650450290609991930929594521921208780899757965398360086992099381832995073955506958821655372681970112562804577530208651675996528617262693958751195285371230790988741041496869140904046414320278189103736789305088489636024127715978439300785989247215275867951013255925809735479471883338351299180591011255281885961242995409072561940244771612447316409017677474822482928698183528232263803799926211692640155689629903898365777273000566450465466723659861801656618726777274689021162957914736922404694190070274236964163273886807208820068271673047750886130307545831668836096290655823576388755329367886670574352063509727295", @@ -234,7 +252,9 @@ async def asyncSetUp(self): self.profile = self.session.profile self.context = self.profile.context setattr(self.profile, "session", mock.MagicMock(return_value=self.session)) - + self.session.wallet.get_public_did = mock.CoroutineMock( + return_value=DIDInfo(TEST_DID, None, None, None, True) + ) # Ledger Ledger = mock.MagicMock() self.ledger = Ledger() @@ -381,7 +401,6 @@ async def test_receive_proposal(self): # Not much to assert. Receive proposal doesn't do anything await self.handler.receive_proposal(cred_ex_record, cred_proposal_message) - @pytest.mark.skip(reason="Anoncreds-break") async def test_create_offer(self): schema_id_parts = SCHEMA_ID.split(":") @@ -423,26 +442,36 @@ async def test_create_offer(self): ) await self.session.storage.add_record(cred_def_record) - self.issuer.create_credential = mock.CoroutineMock( - return_value=json.dumps(VCDI_OFFER) - ) + with mock.patch.object( + test_module, "AnonCredsIssuer", return_value=self.issuer + ) as mock_issuer: + self.issuer.create_credential_offer = mock.CoroutineMock( + return_value=json.dumps( + VCDI_OFFER["binding_method"]["anoncreds_link_secret"] + ) + ) - (cred_format, attachment) = await self.handler.create_offer(cred_proposal) + self.issuer.match_created_credential_definitions = mock.CoroutineMock( + return_value=CRED_DEF_ID + ) - self.issuer.create_credential.assert_called_once_with(CRED_DEF_ID) + (cred_format, attachment) = await self.handler.create_offer(cred_proposal) - # assert identifier match - assert cred_format.attach_id == self.handler.format.api == attachment.ident + self.issuer.create_credential_offer.assert_called_once_with(CRED_DEF_ID) - # assert content of attachment is proposal data - assert attachment.content == VCDI_OFFER + assert cred_format.attach_id == self.handler.format.api == attachment.ident - # assert data is encoded as base64 - assert attachment.data.base64 + assert ( + attachment.content["binding_method"]["anoncreds_link_secret"] + == VCDI_OFFER["binding_method"]["anoncreds_link_secret"] + ) + + # Assert data is encoded as base64 + assert attachment.data.base64 + # TODO: fix this get_public_did return None in the sc + # (cred_format, attachment) = await self.handler.create_offer(cred_proposal) - self.issuer.create_credential_offer.reset_mock() - (cred_format, attachment) = await self.handler.create_offer(cred_proposal) - self.issuer.create_credential_offer.assert_not_called() + # self.issuer.create_credential_offer.assert_not_called() @pytest.mark.skip(reason="Anoncreds-break") async def test_receive_offer(self): @@ -452,9 +481,25 @@ async def test_receive_offer(self): # Not much to assert. Receive offer doesn't do anything await self.handler.receive_offer(cred_ex_record, cred_offer_message) - @pytest.mark.skip(reason="Anoncreds-break") async def test_create_request(self): + # Define your mock credential definition + mock_credential_definition_result = GetCredDefResult( + credential_definition=CredDef( + issuer_id=TEST_DID, schema_id=SCHEMA_ID, type="CL", tag="tag1", value={} + ), + credential_definition_id=CRED_DEF_ID, + resolution_metadata={}, + credential_definition_metadata={}, + ) + mock_creds_registry = mock.MagicMock() + mock_creds_registry.get_credential_definition = mock.AsyncMock( + return_value=mock_credential_definition_result + ) + + # Inject the MagicMock into the context + self.context.injector.bind_instance(AnonCredsRegistry, mock_creds_registry) + holder_did = "did" cred_offer = V20CredOffer( @@ -466,62 +511,67 @@ async def test_create_request(self): ], ) ], - # TODO here offers_attach=[AttachDecorator.data_base64(VCDI_OFFER, ident="0")], ) - cred_ex_record = V20CredExRecordIndy( + cred_ex_record = V20CredExRecord( cred_ex_id="dummy-id", - state=V20CredExRecordIndy.STATE_OFFER_RECEIVED, + state=V20CredExRecord.STATE_OFFER_RECEIVED, cred_offer=cred_offer.serialize(), ) - cred_def = {"cred": "def"} - self.ledger.get_credential_definition = mock.CoroutineMock( - return_value=cred_def - ) - cred_req_meta = {} self.holder.create_credential_request = mock.CoroutineMock( return_value=(json.dumps(VCDI_CRED_REQ), json.dumps(cred_req_meta)) ) - (cred_format, attachment) = await self.handler.create_request( - cred_ex_record, {"holder_did": holder_did} - ) - - self.holder.create_credential_request.assert_called_once_with( - VCDI_OFFER, cred_def, holder_did - ) - - # assert identifier match - assert cred_format.attach_id == self.handler.format.api == attachment.ident + self.profile = mock.MagicMock(AskarAnoncredsProfile) + self.context.injector.bind_instance(AskarAnoncredsProfile, self.profile) + with mock.patch.object( + AnonCredsHolder, "create_credential_request", mock.CoroutineMock() + ) as mock_create: - # assert content of attachment is proposal data - assert attachment.content == VCDI_CRED_REQ + mock_create.return_value = ( + json.dumps(VCDI_CRED_REQ["binding_proof"]["anoncreds_link_secret"]), + json.dumps(cred_req_meta), + ) + (cred_format, attachment) = await self.handler.create_request( + cred_ex_record, {"holder_did": holder_did} + ) - # assert data is encoded as base64 - assert attachment.data.base64 + legacy_offer = await self.handler._prepare_legacy_offer( + VCDI_OFFER, SCHEMA_ID + ) + mock_create.assert_called_once_with( + legacy_offer, + mock_credential_definition_result.credential_definition, + holder_did, + ) + assert cred_format.attach_id == self.handler.format.api == attachment.ident - # cover case with cache (change ID to prevent already exists error) - cred_ex_record._id = "dummy-id2" - await self.handler.create_request(cred_ex_record, {"holder_did": holder_did}) + del VCDI_CRED_REQ["binding_proof"]["anoncreds_link_secret"]["prover_did"] + assert attachment.content == VCDI_CRED_REQ + assert attachment.data.base64 - # cover case with no cache in injection context - self.context.injector.clear_binding(BaseCache) - cred_ex_record._id = "dummy-id3" - self.context.injector.bind_instance( - BaseMultitenantManager, - mock.MagicMock(MultitenantManager, autospec=True), - ) - with mock.patch.object( - IndyLedgerRequestsExecutor, - "get_ledger_for_identifier", - mock.CoroutineMock(return_value=(None, self.ledger)), - ): + cred_ex_record._id = "dummy-id2" await self.handler.create_request( cred_ex_record, {"holder_did": holder_did} ) + self.context.injector.clear_binding(BaseCache) + cred_ex_record._id = "dummy-id3" + self.context.injector.bind_instance( + BaseMultitenantManager, + mock.MagicMock(MultitenantManager, autospec=True), + ) + with mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + mock.CoroutineMock(return_value=(None, self.ledger)), + ): + await self.handler.create_request( + cred_ex_record, {"holder_did": holder_did} + ) + @pytest.mark.skip(reason="Anoncreds-break") async def test_receive_request(self): cred_ex_record = mock.MagicMock() @@ -530,7 +580,6 @@ async def test_receive_request(self): # Not much to assert. Receive request doesn't do anything await self.handler.receive_request(cred_ex_record, cred_request_message) - @pytest.mark.skip(reason="Anoncreds-break") async def test_issue_credential_revocable(self): attr_values = { "legalName": "value", @@ -552,7 +601,6 @@ async def test_issue_credential_revocable(self): ], ) ], - # TODO here offers_attach=[AttachDecorator.data_base64(VCDI_OFFER, ident="0")], ) cred_request = V20CredRequest( @@ -564,59 +612,52 @@ async def test_issue_credential_revocable(self): ], ) ], - # TODO here requests_attach=[AttachDecorator.data_base64(VCDI_CRED_REQ, ident="0")], ) - cred_ex_record = V20CredExRecordIndy( + cred_ex_record = V20CredExRecord( cred_ex_id="dummy-cxid", cred_offer=cred_offer.serialize(), cred_request=cred_request.serialize(), - initiator=V20CredExRecordIndy.INITIATOR_SELF, - role=V20CredExRecordIndy.ROLE_ISSUER, - state=V20CredExRecordIndy.STATE_REQUEST_RECEIVED, + initiator=V20CredExRecord.INITIATOR_SELF, + role=V20CredExRecord.ROLE_ISSUER, + state=V20CredExRecord.STATE_REQUEST_RECEIVED, ) cred_rev_id = "1000" - self.issuer.create_credential = mock.CoroutineMock( - return_value=(json.dumps(VCDI_CRED), cred_rev_id) - ) - - with mock.patch.object(test_module, "IndyRevocation", autospec=True) as revoc: - revoc.return_value.get_or_create_active_registry = mock.CoroutineMock( - return_value=( - mock.MagicMock( # active_rev_reg_rec - revoc_reg_id=REV_REG_ID, - ), - mock.MagicMock( # rev_reg - tails_local_path="dummy-path", - get_or_fetch_local_tails_path=(mock.CoroutineMock()), - max_creds=10, - ), - ) + dummy_registry = "dummy-registry" + expected_credential = json.dumps(VCDI_CRED) + + # Mock AnonCredsRevocation and its method create_credential_w3c + with mock.patch.object( + test_module, "AnonCredsRevocation", autospec=True + ) as MockAnonCredsRevocation: + mock_issuer = MockAnonCredsRevocation.return_value + mock_issuer.create_credential_w3c = mock.CoroutineMock( + return_value=(expected_credential, cred_rev_id, dummy_registry) ) + # Call the method under test (cred_format, attachment) = await self.handler.issue_credential( cred_ex_record, retries=1 ) - - self.issuer.create_credential.assert_called_once_with( - SCHEMA, - VCDI_OFFER, - VCDI_CRED_REQ, + legacy_offer = await self.handler._prepare_legacy_offer( + VCDI_OFFER, SCHEMA_ID + ) + legacy_request = await self.handler._prepare_legacy_request( + VCDI_CRED_REQ, CRED_DEF_ID + ) + # Verify the mocked method was called with the expected parameters + mock_issuer.create_credential_w3c.assert_called_once_with( + legacy_offer, + legacy_request, attr_values, - REV_REG_ID, - "dummy-path", ) - # assert identifier match + # Assert the results are as expected assert cred_format.attach_id == self.handler.format.api == attachment.ident - - # assert content of attachment is proposal data - assert attachment.content == VCDI_CRED - - # assert data is encoded as base64 assert attachment.data.base64 + assert attachment.content == {"credential": VCDI_CRED} @pytest.mark.skip(reason="Anoncreds-break") async def test_issue_credential_non_revocable(self): @@ -702,3 +743,153 @@ async def test_issue_credential_non_revocable(self): # assert data is encoded as base64 assert attachment.data.base64 + + @pytest.mark.asyncio + async def test_match_sent_cred_def_id_error(self): + tag_query = {"tag": "test_tag"} + + with self.assertRaises(V20CredFormatError) as context: + await self.handler._match_sent_cred_def_id(tag_query) + assert "Issuer has no operable cred def for proposal spec " in str( + context.exception + ) + + @pytest.mark.asyncio + async def test_store_credential(self): + attr_values = { + "legalName": "value", + "jurisdictionId": "value", + "incorporationDate": "value", + } + cred_preview = V20CredPreview( + attributes=[ + V20CredAttrSpec(name=k, value=v) for (k, v) in attr_values.items() + ] + ) + cred_offer = V20CredOffer( + credential_preview=cred_preview, + formats=[ + V20CredFormat( + attach_id="0", + format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ + V20CredFormat.Format.VC_DI.api + ], + ) + ], + offers_attach=[AttachDecorator.data_base64(VCDI_OFFER, ident="0")], + ) + cred_request = V20CredRequest( + formats=[ + V20CredFormat( + attach_id="0", + format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ + V20CredFormat.Format.VC_DI.api + ], + ) + ], + requests_attach=[AttachDecorator.data_base64(VCDI_CRED_REQ, ident="0")], + ) + cred_issue = V20CredIssue( + formats=[ + V20CredFormat( + attach_id="0", + format_=ATTACHMENT_FORMAT[CRED_20_ISSUE][ + V20CredFormat.Format.VC_DI.api + ], + ) + ], + credentials_attach=[AttachDecorator.data_base64(VCDI_CRED, ident="0")], + ) + cred_ex_record = V20CredExRecord( + cred_ex_id="dummy-cxid", + cred_offer=cred_offer.serialize(), + cred_request=cred_request.serialize(), + cred_issue=cred_issue.serialize(), + initiator=V20CredExRecord.INITIATOR_SELF, + role=V20CredExRecord.ROLE_ISSUER, + state=V20CredExRecord.STATE_REQUEST_RECEIVED, + ) + cred_id = "dummy-cred-id" + + # Define your mock credential definition + mock_credential_definition_result = GetCredDefResult( + credential_definition=CredDef( + issuer_id=TEST_DID, schema_id=SCHEMA_ID, type="CL", tag="tag1", value={} + ), + credential_definition_id=CRED_DEF_ID, + resolution_metadata={}, + credential_definition_metadata={}, + ) + mock_creds_registry = mock.AsyncMock() + mock_creds_registry.get_credential_definition = mock.AsyncMock( + return_value=mock_credential_definition_result + ) + + revocation_registry = RevRegDef( + cred_def_id=CRED_DEF_ID, + issuer_id=TEST_DID, + tag="tag1", + type="CL_ACCUM", + value={}, + ) + + mock_creds_registry.get_revocation_registry_definition = mock.AsyncMock( + return_value=GetRevRegDefResult( + revocation_registry=revocation_registry, + revocation_registry_id="rr-id", + resolution_metadata={}, + revocation_registry_metadata={}, + ) + ) + # Inject the MagicMock into the context + self.context.injector.bind_instance(AnonCredsRegistry, mock_creds_registry) + self.profile = mock.AsyncMock(AskarAnoncredsProfile) + self.context.injector.bind_instance(AskarAnoncredsProfile, self.profile) + with mock.patch.object( + test_module.AnonCredsRevocation, + "get_or_fetch_local_tails_path", + mock.CoroutineMock(), + ) as mock_get_or_fetch_local_tails_path: + with self.assertRaises(V20CredFormatError) as context: + await self.handler.store_credential(cred_ex_record, cred_id) + assert ( + "No credential exchange didcomm/ detail record found for cred ex id dummy-cxid" + in str(context.exception) + ) + + record = V20CredExRecordIndy( + cred_ex_indy_id="dummy-cxid", + rev_reg_id="rr-id", + cred_ex_id="dummy-cxid", + cred_id_stored=cred_id, + cred_request_metadata="dummy-metadata", + cred_rev_id="0", + ) + + record.save = mock.CoroutineMock() + self.handler.get_detail_record = mock.AsyncMock(return_value=record) + with mock.patch.object( + AnonCredsHolder, + "store_credential_w3c", + mock.AsyncMock(), + ) as mock_store_credential: + # Error case: no cred ex record found + + await self.handler.store_credential(cred_ex_record, cred_id) + + mock_store_credential.assert_called_once_with( + mock_credential_definition_result.credential_definition.serialize(), + VCDI_CRED["credential"], + record.cred_request_metadata, + None, + credential_id=cred_id, + rev_reg_def=revocation_registry.serialize(), + ) + + with mock.patch.object( + AnonCredsHolder, + "store_credential_w3c", + mock.AsyncMock(side_effect=AnonCredsHolderError), + ) as mock_store_credential: + with self.assertRaises(AnonCredsHolderError) as context: + await self.handler.store_credential(cred_ex_record, cred_id) From b2e87f38f5a227b85544965fdab4c9c307e89567 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:48:15 -0700 Subject: [PATCH 14/31] chore(deps): Bump pydid from 0.5.0 to 0.5.1 (#3024) Bumps [pydid](https://github.com/Indicio-tech/pydid) from 0.5.0 to 0.5.1. - [Release notes](https://github.com/Indicio-tech/pydid/releases) - [Commits](https://github.com/Indicio-tech/pydid/compare/v0.5.0...v0.5.1) --- updated-dependencies: - dependency-name: pydid dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: jamshale <31809382+jamshale@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 47596a3f81..c6cbb22f41 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2042,13 +2042,13 @@ files = [ [[package]] name = "pydid" -version = "0.5.0" +version = "0.5.1" description = "Python library for validating, constructing, and representing DIDs and DID Documents" optional = false python-versions = "<4.0.0,>=3.9.0" files = [ - {file = "pydid-0.5.0-py3-none-any.whl", hash = "sha256:2562852d2af98ce1a404d64b0826344d811ad78142927da3a84116f1103eac43"}, - {file = "pydid-0.5.0.tar.gz", hash = "sha256:c97c543e019c469fae0939bab454bedf8e010668e746935e3094e13bdfad28d0"}, + {file = "pydid-0.5.1-py3-none-any.whl", hash = "sha256:be89df79b6267ec1814d49cdd240262c8bbddbfcee9e3aad97a97d521620d6c4"}, + {file = "pydid-0.5.1.tar.gz", hash = "sha256:9489a5fbfbecc8dc864c461bb5a0c664895726ab3ca83daf389d91a10146d5e2"}, ] [package.dependencies] @@ -2881,4 +2881,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "5ec524034c85a7d2497fb43981a95e7d7e3cc356c2ada95f4e943c98899b07cb" +content-hash = "6d8062fafd560981bff72be0f1b35b42926fed94128277224e09fff69de7ef1f" diff --git a/pyproject.toml b/pyproject.toml index 558a6087b1..96bac83888 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ nest_asyncio="~1.6.0" packaging="~23.1" portalocker="~2.8.2" prompt_toolkit=">=2.0.9,<2.1.0" -pydid="^0.5.0" +pydid="^0.5.1" pyjwt="~2.8.0" pyld="^2.0.4" pynacl="~1.5.0" From a4506b852cce722e2d1be166e099903f21c375f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:20:48 -0700 Subject: [PATCH 15/31] chore(deps-dev): Bump pytest from 8.2.1 to 8.2.2 (#3025) Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.1 to 8.2.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.2.1...8.2.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index c6cbb22f41..44cfd7dfea 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2137,13 +2137,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pytest" -version = "8.2.1" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, - {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -2881,4 +2881,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "6d8062fafd560981bff72be0f1b35b42926fed94128277224e09fff69de7ef1f" +content-hash = "e9fb3b52f29bed217e3c3c92b25982184a82497b132d5bc0e0add96142a90865" diff --git a/pyproject.toml b/pyproject.toml index 96bac83888..93c14a282b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ pydevd="1.5.1" pydevd-pycharm="~193.6015.39" # testing -pytest = "^8.2.0" +pytest = "^8.2.2" pytest-asyncio = "^0.23.6" pytest-cov = "^5.0.0" pytest-ruff = "^0.3.2" From 3c2f06e9f071a62041f12b8ba77f05a1af7bf287 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 11 Jun 2024 10:10:58 +0200 Subject: [PATCH 16/31] docs: added section on environment variables Signed-off-by: Richard --- docs/features/DevReadMe.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/features/DevReadMe.md b/docs/features/DevReadMe.md index bfcfb013fd..0490d4e4aa 100644 --- a/docs/features/DevReadMe.md +++ b/docs/features/DevReadMe.md @@ -7,6 +7,7 @@ See the [README](../../README.md) for details about this repository and informat - [Introduction](#introduction) - [Developer Demos](#developer-demos) - [Running](#running) + - [Configuring ACA-PY: Environment Variables](#configuring-aca-py-environment-variables) - [Configuring ACA-PY: Command Line Parameters](#configuring-aca-py-command-line-parameters) - [Docker](#docker) - [Locally Installed](#locally-installed) @@ -42,6 +43,26 @@ To put ACA-Py through its paces at the command line, checkout our [demos](../dem ## Running +### Configuring ACA-PY: Environment Variables + +All CLI parameters in ACA-PY have equivalent environment variables. To convert a CLI argument to an environment +variable: + +1. **Basic Conversion**: Convert the CLI argument to uppercase and prefix it with `ACAPY_`. For example, `--admin` + becomes `ACAPY_ADMIN`. + +2. **Multiple Parameters**: Arguments that take multiple parameters, such as `--admin 0.0.0.0 11000`, should be wrapped + in an array. For example, `ACAPY_ADMIN="[0.0.0.0, 11000]"` +3. **Repeat Parameters**: Arguments like `-it `, which can be repeated, must be wrapped inside + another array and string escaped. For example, instead of: `-it http 0.0.0.0 11000 ws 0.0.0.0 8023` + use: `ACAPY_INBOUND_TRANSPORT=[[\"http\",\"0.0.0.0\",\"11000\"],[\"ws\",\"0.0.0.0\",\"8023\"]]` + +For a comprehensive list of all arguments, argument groups, CLI args, and their environment variable equivalents, please +see +the [argparse.py](https://github.com/hyperledger/aries-cloudagent-python/blob/main/aries_cloudagent/config/argparse.py) +file. + + ### Configuring ACA-PY: Command Line Parameters ACA-Py agent instances are configured through the use of command line From 2b94f68dab1f257645177eb8ac5ac21301151b79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 07:52:30 -0700 Subject: [PATCH 17/31] chore(deps): Update prompt-toolkit requirement in /demo (#3026) Updates the requirements on [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) to permit the latest version. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/2.0.10/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/2.0.9...2.0.10) --- updated-dependencies: - dependency-name: prompt-toolkit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: jamshale <31809382+jamshale@users.noreply.github.com> --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index e6013b0a46..87111d6930 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,5 +1,5 @@ asyncpg~=0.29.0 -prompt_toolkit~=2.0.9 +prompt_toolkit~=2.0.10 web.py~=0.62 pygments~=2.18 qrcode[pil]~=7.4 From d0f447f78e20b9f0cc17dadf18ecf3a3cc8ad7a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 08:02:36 -0700 Subject: [PATCH 18/31] chore(deps): Bump sphinx from 1.8.4 to 1.8.6 (#3021) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 1.8.4 to 1.8.6. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v1.8.4...v1.8.6) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 18 +++++++++--------- pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 44cfd7dfea..ded8b4dcfd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -856,13 +856,13 @@ files = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.9" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] [[package]] @@ -2459,20 +2459,20 @@ files = [ [[package]] name = "sphinx" -version = "1.8.4" +version = "1.8.6" description = "Python documentation generator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Sphinx-1.8.4-py2.py3-none-any.whl", hash = "sha256:b53904fa7cb4b06a39409a492b949193a1b68cc7241a1a8ce9974f86f0d24287"}, - {file = "Sphinx-1.8.4.tar.gz", hash = "sha256:c1c00fc4f6e8b101a0d037065043460dffc2d507257f2f11acaed71fd2b0c83c"}, + {file = "Sphinx-1.8.6-py2.py3-none-any.whl", hash = "sha256:5973adbb19a5de30e15ab394ec8bc05700317fa83f122c349dd01804d983720f"}, + {file = "Sphinx-1.8.6.tar.gz", hash = "sha256:e096b1b369dbb0fcb95a31ba8c9e1ae98c588e601f08eada032248e1696de4b1"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3,<2.0 || >2.0" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.11" +docutils = ">=0.11,<0.18" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -2881,4 +2881,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "e9fb3b52f29bed217e3c3c92b25982184a82497b132d5bc0e0add96142a90865" +content-hash = "e23d53b1e134b975a054cd296099b7f00a2c0850941e1ca80fdfb85d60aa1fde" diff --git a/pyproject.toml b/pyproject.toml index 93c14a282b..f2579a2b1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ ruff = "0.4.4" # Sync with version in .pre-commit-config.yaml black = "24.4.2" -sphinx="1.8.4" +sphinx="1.8.6" sphinx-rtd-theme=">=0.4.3" ptvsd="4.3.2" From 586a30d67d9072cf5a6a936ce26a451afce2641b Mon Sep 17 00:00:00 2001 From: Mourits de Beer <31511766+ff137@users.noreply.github.com> Date: Tue, 11 Jun 2024 19:47:53 +0200 Subject: [PATCH 19/31] :sparkles: Adds support for paginated storage queries, and implements pagination for the wallets_list endpoint (#3000) * :art: name the scan args Signed-off-by: ff137 * :art: add return types and arg docstrings Signed-off-by: ff137 * :white_check_mark: fix assertion Signed-off-by: ff137 * :spark: implement `find_paginated_records` Signed-off-by: ff137 * :sparkles: add `limit` and `offset` to BaseRecord.query, calling `find_paginated_records` if requested Signed-off-by: ff137 * :sparkles: add `limit` and `offset` to wallet list query. define `MAXIMUM_PAGE_SIZE` and use in validation. use `limit` and `offset` in records query Signed-off-by: ff137 * :art: organise imports Signed-off-by: ff137 * :white_check_mark: fix assertion Signed-off-by: ff137 * :art: correction: call `scan` on `store` Signed-off-by: ff137 * :construction_worker: Don't run integration tests when PR is in draft Signed-off-by: ff137 * :art: call `scan` on `profile.opened.store` Signed-off-by: ff137 * :art: remove unused options arg Signed-off-by: ff137 * :art: NB: remove unused option `retrieveTags`. Unimplemented / has no effect Signed-off-by: ff137 * :art: fix accessor for the store instance Signed-off-by: ff137 * :art: add direct instantiation of `self._profile`, so that `self._profile.store` reference is resolvable Signed-off-by: ff137 * :sparkles: add optional limit and offset to AskarStorageSearchSession.fetch Signed-off-by: ff137 * :sparkles: implement PaginatedQuerySchema class for deduplication Signed-off-by: ff137 * :white_check_mark: add tests for BaseRecord.query when called with limit or offset Signed-off-by: ff137 * :art: organise imports Signed-off-by: ff137 * :art: efficient subset of OrderedDict values Signed-off-by: ff137 * :white_check_mark: test coverage for find_paginated_records Signed-off-by: ff137 * :art: organise imports Signed-off-by: ff137 * :white_check_mark: test coverage for PaginatedQuerySchema Signed-off-by: ff137 * :art: fix marshmallow warning Signed-off-by: ff137 * :bug: fix reference to store, and add _profile to init for type hint Signed-off-by: ff137 * Update lock file Signed-off-by: ff137 --------- Signed-off-by: ff137 Co-authored-by: jamshale <31809382+jamshale@users.noreply.github.com> --- .github/workflows/integrationtests.yml | 2 +- aries_cloudagent/anoncreds/holder.py | 20 +- aries_cloudagent/askar/profile.py | 3 +- aries_cloudagent/askar/profile_anon.py | 3 +- aries_cloudagent/indy/credx/holder.py | 20 +- .../messaging/models/base_record.py | 35 ++- .../messaging/models/paginated_query.py | 31 ++ .../models/tests/test_base_record.py | 110 ++++++- .../models/tests/test_paginated_query.py | 67 +++++ aries_cloudagent/multitenant/admin/routes.py | 14 +- aries_cloudagent/storage/askar.py | 57 +++- aries_cloudagent/storage/base.py | 40 ++- aries_cloudagent/storage/in_memory.py | 37 ++- .../storage/tests/test_askar_storage.py | 6 +- .../storage/tests/test_in_memory_storage.py | 32 +- poetry.lock | 282 +++++++++--------- 16 files changed, 541 insertions(+), 218 deletions(-) create mode 100644 aries_cloudagent/messaging/models/paginated_query.py create mode 100644 aries_cloudagent/messaging/models/tests/test_paginated_query.py diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index b7e3eb8344..cefa6fc430 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -19,7 +19,7 @@ defaults: jobs: test: runs-on: ubuntu-latest - if: (github.event_name == 'pull_request' && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') + if: (github.event_name == 'pull_request' && !github.event.pull_request.draft && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') outputs: is_release: ${{ steps.check_if_release.outputs.is_release }} steps: diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index ca4a4c3539..2c08bbf188 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -350,11 +350,11 @@ async def get_credentials(self, start: int, count: int, wql: dict): try: rows = self.profile.store.scan( - CATEGORY_CREDENTIAL, - wql, - start, - count, - self.profile.settings.get("wallet.askar_profile"), + category=CATEGORY_CREDENTIAL, + tag_filter=wql, + offset=start, + limit=count, + profile=self.profile.settings.get("wallet.askar_profile"), ) async for row in rows: cred = Credential.load(row.raw_value) @@ -424,11 +424,11 @@ async def get_credentials_for_presentation_request_by_referent( tag_filter = {"$and": [tag_filter, extra_query]} rows = self.profile.store.scan( - CATEGORY_CREDENTIAL, - tag_filter, - start, - count, - self.profile.settings.get("wallet.askar_profile"), + category=CATEGORY_CREDENTIAL, + tag_filter=tag_filter, + offset=start, + limit=count, + profile=self.profile.settings.get("wallet.askar_profile"), ) async for row in rows: if row.name in creds: diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index b07dcf1f6b..779eec83a7 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -207,6 +207,7 @@ def __init__( self._opener = self.profile.store.transaction(profile.profile_id) else: self._opener = self.profile.store.session(profile.profile_id) + self._profile = profile self._handle: Session = None self._acquire_start: float = None self._acquire_end: float = None @@ -219,7 +220,7 @@ def handle(self) -> Session: @property def store(self) -> Store: """Accessor for the Store instance.""" - return self._handle and self._handle.store + return self._profile and self._profile.store @property def is_transaction(self) -> bool: diff --git a/aries_cloudagent/askar/profile_anon.py b/aries_cloudagent/askar/profile_anon.py index 2044abeb37..3e1ce59e47 100644 --- a/aries_cloudagent/askar/profile_anon.py +++ b/aries_cloudagent/askar/profile_anon.py @@ -202,6 +202,7 @@ def __init__( self._opener = self.profile.store.transaction(profile.profile_id) else: self._opener = self.profile.store.session(profile.profile_id) + self._profile = profile self._handle: Session = None self._acquire_start: float = None self._acquire_end: float = None @@ -214,7 +215,7 @@ def handle(self) -> Session: @property def store(self) -> Store: """Accessor for the Store instance.""" - return self._handle and self._handle.store + return self._profile and self._profile.store @property def is_transaction(self) -> bool: diff --git a/aries_cloudagent/indy/credx/holder.py b/aries_cloudagent/indy/credx/holder.py index 7eda477671..616f3bdaaf 100644 --- a/aries_cloudagent/indy/credx/holder.py +++ b/aries_cloudagent/indy/credx/holder.py @@ -250,11 +250,11 @@ async def get_credentials(self, start: int, count: int, wql: dict): try: rows = self._profile.store.scan( - CATEGORY_CREDENTIAL, - wql, - start, - count, - self._profile.settings.get("wallet.askar_profile"), + category=CATEGORY_CREDENTIAL, + tag_filter=wql, + offset=start, + limit=count, + profile=self._profile.settings.get("wallet.askar_profile"), ) async for row in rows: cred = Credential.load(row.raw_value) @@ -321,11 +321,11 @@ async def get_credentials_for_presentation_request_by_referent( tag_filter = {"$and": [tag_filter, extra_query]} rows = self._profile.store.scan( - CATEGORY_CREDENTIAL, - tag_filter, - start, - count, - self._profile.settings.get("wallet.askar_profile"), + category=CATEGORY_CREDENTIAL, + tag_filter=tag_filter, + offset=start, + limit=count, + profile=self._profile.settings.get("wallet.askar_profile"), ) async for row in rows: if row.name in creds: diff --git a/aries_cloudagent/messaging/models/base_record.py b/aries_cloudagent/messaging/models/base_record.py index a61ffedb6c..08f6b9c09b 100644 --- a/aries_cloudagent/messaging/models/base_record.py +++ b/aries_cloudagent/messaging/models/base_record.py @@ -12,7 +12,12 @@ from ...cache.base import BaseCache from ...config.settings import BaseSettings from ...core.profile import ProfileSession -from ...storage.base import BaseStorage, StorageDuplicateError, StorageNotFoundError +from ...storage.base import ( + DEFAULT_PAGE_SIZE, + BaseStorage, + StorageDuplicateError, + StorageNotFoundError, +) from ...storage.record import StorageRecord from ..util import datetime_to_str, time_now from ..valid import INDY_ISO8601_DATETIME_EXAMPLE, INDY_ISO8601_DATETIME_VALIDATE @@ -228,7 +233,7 @@ async def retrieve_by_id( storage = session.inject(BaseStorage) result = await storage.get_record( - cls.RECORD_TYPE, record_id, {"forUpdate": for_update, "retrieveTags": False} + cls.RECORD_TYPE, record_id, options={"forUpdate": for_update} ) vals = json.loads(result.value) return cls.from_storage(record_id, vals) @@ -255,7 +260,7 @@ async def retrieve_by_tag_filter( rows = await storage.find_all_records( cls.RECORD_TYPE, cls.prefix_tag_filter(tag_filter), - options={"forUpdate": for_update, "retrieveTags": False}, + options={"forUpdate": for_update}, ) found = None for record in rows: @@ -284,6 +289,8 @@ async def query( session: ProfileSession, tag_filter: dict = None, *, + limit: Optional[int] = None, + offset: Optional[int] = None, post_filter_positive: dict = None, post_filter_negative: dict = None, alt: bool = False, @@ -293,6 +300,8 @@ async def query( Args: session: The profile session to use tag_filter: An optional dictionary of tag filter clauses + limit: The maximum number of records to retrieve + offset: The offset to start retrieving records from post_filter_positive: Additional value filters to apply matching positively post_filter_negative: Additional value filters to apply matching negatively alt: set to match any (positive=True) value or miss all (positive=False) @@ -300,11 +309,21 @@ async def query( """ storage = session.inject(BaseStorage) - rows = await storage.find_all_records( - cls.RECORD_TYPE, - cls.prefix_tag_filter(tag_filter), - options={"retrieveTags": False}, - ) + + tag_query = cls.prefix_tag_filter(tag_filter) + if limit is not None or offset is not None: + rows = await storage.find_paginated_records( + type_filter=cls.RECORD_TYPE, + tag_query=tag_query, + limit=limit or DEFAULT_PAGE_SIZE, + offset=offset or 0, + ) + else: + rows = await storage.find_all_records( + type_filter=cls.RECORD_TYPE, + tag_query=tag_query, + ) + result = [] for record in rows: vals = json.loads(record.value) diff --git a/aries_cloudagent/messaging/models/paginated_query.py b/aries_cloudagent/messaging/models/paginated_query.py new file mode 100644 index 0000000000..bc05b1448e --- /dev/null +++ b/aries_cloudagent/messaging/models/paginated_query.py @@ -0,0 +1,31 @@ +"""Class for paginated query parameters.""" + +from marshmallow import fields + +from aries_cloudagent.storage.base import DEFAULT_PAGE_SIZE, MAXIMUM_PAGE_SIZE + +from ...messaging.models.openapi import OpenAPISchema + + +class PaginatedQuerySchema(OpenAPISchema): + """Parameters for paginated queries.""" + + limit = fields.Int( + required=False, + load_default=DEFAULT_PAGE_SIZE, + validate=lambda x: x > 0 and x <= MAXIMUM_PAGE_SIZE, + metadata={"description": "Number of results to return", "example": 50}, + error_messages={ + "validator_failed": ( + "Value must be greater than 0 and " + f"less than or equal to {MAXIMUM_PAGE_SIZE}" + ) + }, + ) + offset = fields.Int( + required=False, + load_default=0, + validate=lambda x: x >= 0, + metadata={"description": "Offset for pagination", "example": 0}, + error_messages={"validator_failed": "Value must be 0 or greater"}, + ) diff --git a/aries_cloudagent/messaging/models/tests/test_base_record.py b/aries_cloudagent/messaging/models/tests/test_base_record.py index 23148bf2ca..9a8fd8c3f8 100644 --- a/aries_cloudagent/messaging/models/tests/test_base_record.py +++ b/aries_cloudagent/messaging/models/tests/test_base_record.py @@ -1,21 +1,21 @@ import json - -from aries_cloudagent.tests import mock from unittest import IsolatedAsyncioTestCase + from marshmallow import EXCLUDE, fields +from aries_cloudagent.tests import mock + from ....cache.base import BaseCache -from ....core.event_bus import EventBus, MockEventBus, Event +from ....core.event_bus import Event, EventBus, MockEventBus from ....core.in_memory import InMemoryProfile +from ....messaging.models.base import BaseModelError from ....storage.base import ( + DEFAULT_PAGE_SIZE, BaseStorage, StorageDuplicateError, StorageRecord, ) -from ....messaging.models.base import BaseModelError - from ...util import time_now - from ..base_record import BaseRecord, BaseRecordSchema @@ -170,7 +170,8 @@ async def test_query(self): mock_storage.find_all_records.return_value = [stored] result = await BaseRecordImpl.query(session, tag_filter) mock_storage.find_all_records.assert_awaited_once_with( - BaseRecordImpl.RECORD_TYPE, tag_filter, options={"retrieveTags": False} + type_filter=BaseRecordImpl.RECORD_TYPE, + tag_query=tag_filter, ) assert result and isinstance(result[0], BaseRecordImpl) assert result[0]._id == record_id @@ -220,9 +221,8 @@ async def test_query_post_filter(self): session, tag_filter, post_filter_positive={"a": "one"} ) mock_storage.find_all_records.assert_awaited_once_with( - ARecordImpl.RECORD_TYPE, - tag_filter, - options={"retrieveTags": False}, + type_filter=ARecordImpl.RECORD_TYPE, + tag_query=tag_filter, ) assert result and isinstance(result[0], ARecordImpl) assert result[0]._id == record_id @@ -323,3 +323,93 @@ async def test_tag_prefix(self): assert UnencTestImpl.prefix_tag_filter(tags) == { "$or": [{"~a": "x"}, {"c": "z"}] } + + async def test_query_with_limit(self): + session = InMemoryProfile.test_session() + mock_storage = mock.MagicMock(BaseStorage, autospec=True) + session.context.injector.bind_instance(BaseStorage, mock_storage) + record_id = "record_id" + a_record = ARecordImpl(ident=record_id, a="one", b="two", code="red") + record_value = a_record.record_value + record_value.update({"created_at": time_now(), "updated_at": time_now()}) + tag_filter = {"code": "red"} + stored = StorageRecord( + ARecordImpl.RECORD_TYPE, + json.dumps(record_value), + {"code": "red"}, + record_id, + ) + mock_storage.find_paginated_records.return_value = [stored] + + # Query with limit + result = await ARecordImpl.query(session, tag_filter, limit=10) + mock_storage.find_paginated_records.assert_awaited_once_with( + type_filter=ARecordImpl.RECORD_TYPE, + tag_query=tag_filter, + limit=10, + offset=0, + ) + assert result and isinstance(result[0], ARecordImpl) + assert result[0]._id == record_id + assert result[0].value == record_value + assert result[0].a == "one" + + async def test_query_with_offset(self): + session = InMemoryProfile.test_session() + mock_storage = mock.MagicMock(BaseStorage, autospec=True) + session.context.injector.bind_instance(BaseStorage, mock_storage) + record_id = "record_id" + a_record = ARecordImpl(ident=record_id, a="one", b="two", code="red") + record_value = a_record.record_value + record_value.update({"created_at": time_now(), "updated_at": time_now()}) + tag_filter = {"code": "red"} + stored = StorageRecord( + ARecordImpl.RECORD_TYPE, + json.dumps(record_value), + {"code": "red"}, + record_id, + ) + mock_storage.find_paginated_records.return_value = [stored] + + # Query with offset + result = await ARecordImpl.query(session, tag_filter, offset=10) + mock_storage.find_paginated_records.assert_awaited_once_with( + type_filter=ARecordImpl.RECORD_TYPE, + tag_query=tag_filter, + limit=DEFAULT_PAGE_SIZE, + offset=10, + ) + assert result and isinstance(result[0], ARecordImpl) + assert result[0]._id == record_id + assert result[0].value == record_value + assert result[0].a == "one" + + async def test_query_with_limit_and_offset(self): + session = InMemoryProfile.test_session() + mock_storage = mock.MagicMock(BaseStorage, autospec=True) + session.context.injector.bind_instance(BaseStorage, mock_storage) + record_id = "record_id" + a_record = ARecordImpl(ident=record_id, a="one", b="two", code="red") + record_value = a_record.record_value + record_value.update({"created_at": time_now(), "updated_at": time_now()}) + tag_filter = {"code": "red"} + stored = StorageRecord( + ARecordImpl.RECORD_TYPE, + json.dumps(record_value), + {"code": "red"}, + record_id, + ) + mock_storage.find_paginated_records.return_value = [stored] + + # Query with limit and offset + result = await ARecordImpl.query(session, tag_filter, limit=10, offset=5) + mock_storage.find_paginated_records.assert_awaited_once_with( + type_filter=ARecordImpl.RECORD_TYPE, + tag_query=tag_filter, + limit=10, + offset=5, + ) + assert result and isinstance(result[0], ARecordImpl) + assert result[0]._id == record_id + assert result[0].value == record_value + assert result[0].a == "one" diff --git a/aries_cloudagent/messaging/models/tests/test_paginated_query.py b/aries_cloudagent/messaging/models/tests/test_paginated_query.py new file mode 100644 index 0000000000..d7e6eda951 --- /dev/null +++ b/aries_cloudagent/messaging/models/tests/test_paginated_query.py @@ -0,0 +1,67 @@ +import pytest +from marshmallow import ValidationError + +from aries_cloudagent.storage.base import DEFAULT_PAGE_SIZE, MAXIMUM_PAGE_SIZE + +from ..paginated_query import PaginatedQuerySchema + + +def test_paginated_query_schema_defaults(): + schema = PaginatedQuerySchema() + result = schema.load({}) + assert result["limit"] == DEFAULT_PAGE_SIZE + assert result["offset"] == 0 + + +def test_paginated_query_schema_not_required(): + schema = PaginatedQuerySchema() + result = schema.load({"limit": 10}) + assert result["limit"] == 10 + assert result["offset"] == 0 + + result = schema.load({"offset": 5}) + assert result["limit"] == DEFAULT_PAGE_SIZE + assert result["offset"] == 5 + + +def test_paginated_query_schema_limit_validation(): + schema = PaginatedQuerySchema() + + # Valid limit + result = schema.load({"limit": 1}) + assert result["limit"] == 1 + + result = schema.load({"limit": MAXIMUM_PAGE_SIZE}) + assert result["limit"] == MAXIMUM_PAGE_SIZE + + # Invalid limit (less than 1) + with pytest.raises(ValidationError) as exc_info: + schema.load({"limit": 0}) + assert ( + f"Value must be greater than 0 and less than or equal to {MAXIMUM_PAGE_SIZE}" + in str(exc_info.value) + ) + + # Invalid limit (greater than MAXIMUM_PAGE_SIZE) + with pytest.raises(ValidationError) as exc_info: + schema.load({"limit": MAXIMUM_PAGE_SIZE + 1}) + assert ( + f"Value must be greater than 0 and less than or equal to {MAXIMUM_PAGE_SIZE}" + in str(exc_info.value) + ) + + +def test_paginated_query_schema_offset_validation(): + schema = PaginatedQuerySchema() + + # Valid offset + result = schema.load({"offset": 0}) + assert result["offset"] == 0 + + result = schema.load({"offset": 10}) + assert result["offset"] == 10 + + # Invalid offset (less than 0) + with pytest.raises(ValidationError) as exc_info: + schema.load({"offset": -1}) + assert "Value must be 0 or greater" in str(exc_info.value) diff --git a/aries_cloudagent/multitenant/admin/routes.py b/aries_cloudagent/multitenant/admin/routes.py index 1e84020d98..96b6a6653a 100644 --- a/aries_cloudagent/multitenant/admin/routes.py +++ b/aries_cloudagent/multitenant/admin/routes.py @@ -16,8 +16,10 @@ from ...core.profile import ProfileManagerProvider from ...messaging.models.base import BaseModelError from ...messaging.models.openapi import OpenAPISchema +from ...messaging.models.paginated_query import PaginatedQuerySchema from ...messaging.valid import UUID4_EXAMPLE, JSONWebToken from ...multitenant.base import BaseMultitenantManager +from ...storage.base import DEFAULT_PAGE_SIZE from ...storage.error import StorageError, StorageNotFoundError from ...utils.endorsement_setup import attempt_auto_author_with_endorser_setup from ...utils.profiles import subwallet_type_not_same_as_base_wallet_raise_web_exception @@ -353,7 +355,7 @@ class WalletListSchema(OpenAPISchema): ) -class WalletListQueryStringSchema(OpenAPISchema): +class WalletListQueryStringSchema(PaginatedQuerySchema): """Parameters and validators for wallet list request query string.""" wallet_name = fields.Str( @@ -380,9 +382,17 @@ async def wallets_list(request: web.BaseRequest): if wallet_name: query["wallet_name"] = wallet_name + limit = int(request.query.get("limit", DEFAULT_PAGE_SIZE)) + offset = int(request.query.get("offset", 0)) + try: async with profile.session() as session: - records = await WalletRecord.query(session, tag_filter=query) + records = await WalletRecord.query( + session, + tag_filter=query, + limit=limit, + offset=offset, + ) results = [format_wallet_record(record) for record in records] results.sort(key=lambda w: w["created_at"]) except (StorageError, BaseModelError) as err: diff --git a/aries_cloudagent/storage/askar.py b/aries_cloudagent/storage/askar.py index bab4cc4ff7..6f1b62ab8e 100644 --- a/aries_cloudagent/storage/askar.py +++ b/aries_cloudagent/storage/askar.py @@ -1,11 +1,10 @@ """Aries-Askar implementation of BaseStorage interface.""" -from typing import Mapping, Sequence +from typing import Mapping, Optional, Sequence from aries_askar import AskarError, AskarErrorCode, Session from ..askar.profile import AskarProfile, AskarProfileSession - from .base import ( DEFAULT_PAGE_SIZE, BaseStorage, @@ -14,8 +13,8 @@ validate_record, ) from .error import ( - StorageError, StorageDuplicateError, + StorageError, StorageNotFoundError, StorageSearchError, ) @@ -169,6 +168,40 @@ async def find_record( tags=row.tags, ) + async def find_paginated_records( + self, + type_filter: str, + tag_query: Mapping = None, + limit: int = DEFAULT_PAGE_SIZE, + offset: int = 0, + ) -> Sequence[StorageRecord]: + """Retrieve a page of records matching a particular type filter and tag query. + + Args: + type_filter: The type of records to filter by + tag_query: An optional dictionary of tag filter clauses + limit: The maximum number of records to retrieve + offset: The offset to start retrieving records from + """ + results = [] + + async for row in self._session.store.scan( + category=type_filter, + tag_filter=tag_query, + limit=limit, + offset=offset, + profile=self._session.profile.settings.get("wallet.askar_profile"), + ): + results += ( + StorageRecord( + type=row.category, + id=row.name, + value=None if row.value is None else row.value.decode("utf-8"), + tags=row.tags, + ), + ) + return results + async def find_all_records( self, type_filter: str, @@ -307,11 +340,14 @@ async def __anext__(self): tags=row.tags, ) - async def fetch(self, max_count: int = None) -> Sequence[StorageRecord]: + async def fetch( + self, max_count: Optional[int] = None, offset: Optional[int] = None + ) -> Sequence[StorageRecord]: """Fetch the next list of results from the store. Args: max_count: Max number of records to return + offset: The offset to start retrieving records from Returns: A list of `StorageRecord` instances @@ -322,11 +358,12 @@ async def fetch(self, max_count: int = None) -> Sequence[StorageRecord]: """ if self._done: raise StorageSearchError("Search query is complete") - await self._open() + + limit = max_count or self.page_size + await self._open(limit=limit, offset=offset) count = 0 ret = [] - limit = max_count or self.page_size while count < limit: try: @@ -352,14 +389,16 @@ async def fetch(self, max_count: int = None) -> Sequence[StorageRecord]: return ret - async def _open(self): + async def _open(self, offset: Optional[int] = None, limit: Optional[int] = None): """Start the search query.""" if self._scan: return try: self._scan = self._profile.store.scan( - self.type_filter, - self.tag_query, + category=self.type_filter, + tag_filter=self.tag_query, + offset=offset, + limit=limit, profile=self._profile.settings.get("wallet.askar_profile"), ) except AskarError as err: diff --git a/aries_cloudagent/storage/base.py b/aries_cloudagent/storage/base.py index 8eb798ffbf..2035e071e0 100644 --- a/aries_cloudagent/storage/base.py +++ b/aries_cloudagent/storage/base.py @@ -3,11 +3,11 @@ from abc import ABC, abstractmethod from typing import Mapping, Sequence -from .error import StorageError, StorageDuplicateError, StorageNotFoundError +from .error import StorageDuplicateError, StorageError, StorageNotFoundError from .record import StorageRecord - DEFAULT_PAGE_SIZE = 100 +MAXIMUM_PAGE_SIZE = 10000 def validate_record(record: StorageRecord, *, delete=False): @@ -89,22 +89,50 @@ async def find_record( raise StorageDuplicateError("Duplicate records found") return results[0] + @abstractmethod + async def find_paginated_records( + self, + type_filter: str, + tag_query: Mapping = None, + limit: int = DEFAULT_PAGE_SIZE, + offset: int = 0, + ) -> Sequence[StorageRecord]: + """Retrieve a page of records matching a particular type filter and tag query. + + Args: + type_filter: The type of records to filter by + tag_query: An optional dictionary of tag filter clauses + limit: The maximum number of records to retrieve + offset: The offset to start retrieving records from + """ + @abstractmethod async def find_all_records( self, type_filter: str, tag_query: Mapping = None, options: Mapping = None, - ): - """Retrieve all records matching a particular type filter and tag query.""" + ) -> Sequence[StorageRecord]: + """Retrieve all records matching a particular type filter and tag query. + + Args: + type_filter: The type of records to filter by. + tag_query: An optional dictionary of tag filter clauses. + options: Additional options for the query. + """ @abstractmethod async def delete_all_records( self, type_filter: str, tag_query: Mapping = None, - ): - """Remove all records matching a particular type filter and tag query.""" + ) -> None: + """Remove all records matching a particular type filter and tag query. + + Args: + type_filter: The type of records to filter by. + tag_query: An optional dictionary of tag filter clauses. + """ class BaseStorageSearch(ABC): diff --git a/aries_cloudagent/storage/in_memory.py b/aries_cloudagent/storage/in_memory.py index d2b0671f4f..68a166cea1 100644 --- a/aries_cloudagent/storage/in_memory.py +++ b/aries_cloudagent/storage/in_memory.py @@ -3,7 +3,6 @@ from typing import Mapping, Sequence from ..core.in_memory import InMemoryProfile - from .base import ( DEFAULT_PAGE_SIZE, BaseStorage, @@ -11,11 +10,7 @@ BaseStorageSearchSession, validate_record, ) -from .error import ( - StorageDuplicateError, - StorageNotFoundError, - StorageSearchError, -) +from .error import StorageDuplicateError, StorageNotFoundError, StorageSearchError from .record import StorageRecord @@ -102,6 +97,36 @@ async def delete_record(self, record: StorageRecord): raise StorageNotFoundError("Record not found: {}".format(record.id)) del self.profile.records[record.id] + async def find_paginated_records( + self, + type_filter: str, + tag_query: Mapping = None, + limit: int = DEFAULT_PAGE_SIZE, + offset: int = 0, + ) -> Sequence[StorageRecord]: + """Retrieve a page of records matching a particular type filter and tag query. + + Args: + type_filter: The type of records to filter by + tag_query: An optional dictionary of tag filter clauses + limit: The maximum number of records to retrieve + offset: The offset to start retrieving records from + """ + results = [] + skipped = 0 + collected = 0 + for record in self.profile.records.values(): + if record.type == type_filter and tag_query_match(record.tags, tag_query): + if skipped < offset: + skipped += 1 + continue + if collected < limit: + collected += 1 + results.append(record) + else: + break + return results + async def find_all_records( self, type_filter: str, diff --git a/aries_cloudagent/storage/tests/test_askar_storage.py b/aries_cloudagent/storage/tests/test_askar_storage.py index f257c60180..8610f98f39 100644 --- a/aries_cloudagent/storage/tests/test_askar_storage.py +++ b/aries_cloudagent/storage/tests/test_askar_storage.py @@ -376,5 +376,9 @@ async def test_askar_storage_search_session(self): assert storageSearchSession._scan == askar_profile_scan askar_profile.settings.get.assert_called_once_with("wallet.askar_profile") askar_profile.store.scan.assert_called_once_with( - "filter", "tagQuery", profile=profile + category="filter", + tag_filter="tagQuery", + limit=None, + offset=None, + profile=profile, ) diff --git a/aries_cloudagent/storage/tests/test_in_memory_storage.py b/aries_cloudagent/storage/tests/test_in_memory_storage.py index 3d32a16930..2bc777a77e 100644 --- a/aries_cloudagent/storage/tests/test_in_memory_storage.py +++ b/aries_cloudagent/storage/tests/test_in_memory_storage.py @@ -9,11 +9,7 @@ StorageNotFoundError, StorageSearchError, ) -from ...storage.in_memory import ( - InMemoryStorage, - tag_value_match, - tag_query_match, -) +from ...storage.in_memory import InMemoryStorage, tag_query_match, tag_value_match from ...storage.record import StorageRecord @@ -133,6 +129,32 @@ async def test_find_all(self, store, record_factory): assert found.value == record.value assert found.tags == record.tags + @pytest.mark.asyncio + async def test_find_paginated_records(self, store, record_factory): + stored_records = [record_factory(tags={"index": str(i)}) for i in range(10)] + for record in stored_records: + await store.add_record(record) + + # retrieve first 5 records + rows_1 = await store.find_paginated_records("TYPE", {}, limit=5, offset=0) + assert len(rows_1) == 5 # We expect 5 rows to be returned + assert all(record in stored_records for record in rows_1) # All records valid + + # retrieve next 5 records + rows_2 = await store.find_paginated_records("TYPE", {}, limit=5, offset=5) + assert len(rows_2) == 5 + assert all(record in stored_records for record in rows_2) # All records valid + assert all(record not in rows_1 for record in rows_2) # Not repeating records + + # retrieve with limit greater than available records + rows_3 = await store.find_paginated_records("TYPE", {}, limit=15, offset=0) + assert len(rows_3) == 10 # returns all available records + assert all(record in stored_records for record in rows_3) # All records valid + + # retrieve with offset beyond available records + rows_empty = await store.find_paginated_records("TYPE", {}, limit=5, offset=15) + assert len(rows_empty) == 0 + @pytest.mark.asyncio async def test_delete_all(self, store, record_factory): record = record_factory({"tag": "one"}) diff --git a/poetry.lock b/poetry.lock index ded8b4dcfd..a3d9b0b08f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohttp" @@ -633,43 +633,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.7" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, - {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, - {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, - {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, - {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, - {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, - {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, - {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] @@ -903,15 +903,18 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-typing" -version = "4.2.3" +version = "4.3.0" description = "eth-typing: Common type annotations for ethereum python packages" optional = false python-versions = "<4,>=3.8" files = [ - {file = "eth_typing-4.2.3-py3-none-any.whl", hash = "sha256:b2df49fa89d2e85f2cc3fb1c903b0cd183d524f7a045e3db8cc720cf41adcd3d"}, - {file = "eth_typing-4.2.3.tar.gz", hash = "sha256:8ee3ae7d4136d14fcb955c34f9dbef8e52170984d4dc68c0ab0d61621eab29d8"}, + {file = "eth_typing-4.3.0-py3-none-any.whl", hash = "sha256:718f8ef8180ac1a15e476f072e4522e7bd4429bdabc71499e3ca79e2219d775c"}, + {file = "eth_typing-4.3.0.tar.gz", hash = "sha256:3f4eface387eefa68761b23743baab8d413d609b4201c4086c64a006be5dbf53"}, ] +[package.dependencies] +typing-extensions = ">=4.0.0" + [package.extras] dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] @@ -1006,8 +1009,6 @@ files = [ {file = "frozendict-2.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d13b4310db337f4d2103867c5a05090b22bc4d50ca842093779ef541ea9c9eea"}, {file = "frozendict-2.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:b3b967d5065872e27b06f785a80c0ed0a45d1f7c9b85223da05358e734d858ca"}, {file = "frozendict-2.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:4ae8d05c8d0b6134bfb6bfb369d5fa0c4df21eabb5ca7f645af95fdc6689678e"}, - {file = "frozendict-2.4.4-py311-none-any.whl", hash = "sha256:705efca8d74d3facbb6ace80ab3afdd28eb8a237bfb4063ed89996b024bc443d"}, - {file = "frozendict-2.4.4-py312-none-any.whl", hash = "sha256:d9647563e76adb05b7cde2172403123380871360a114f546b4ae1704510801e5"}, {file = "frozendict-2.4.4.tar.gz", hash = "sha256:3f7c031b26e4ee6a3f786ceb5e3abf1181c4ade92dce1f847da26ea2c96008c7"}, ] @@ -1327,13 +1328,9 @@ files = [ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, - {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, - {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, @@ -1643,13 +1640,13 @@ files = [ [[package]] name = "nodeenv" -version = "1.9.0" +version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.9.0-py2.py3-none-any.whl", hash = "sha256:508ecec98f9f3330b636d4448c0f1a56fc68017c68f1e7857ebc52acf0eb879a"}, - {file = "nodeenv-1.9.0.tar.gz", hash = "sha256:07f144e90dae547bf0d4ee8da0ee42664a42a04e02ed68e06324348dafe4bdb1"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] [[package]] @@ -1902,18 +1899,18 @@ files = [ [[package]] name = "pydantic" -version = "2.7.2" +version = "2.7.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.2-py3-none-any.whl", hash = "sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"}, - {file = "pydantic-2.7.2.tar.gz", hash = "sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7"}, + {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, + {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.3" +pydantic-core = "2.18.4" typing-extensions = ">=4.6.1" [package.extras] @@ -1921,90 +1918,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.3" +version = "2.18.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c"}, - {file = "pydantic_core-2.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6"}, - {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426"}, - {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812"}, - {file = "pydantic_core-2.18.3-cp310-none-win32.whl", hash = "sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779"}, - {file = "pydantic_core-2.18.3-cp310-none-win_amd64.whl", hash = "sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0"}, - {file = "pydantic_core-2.18.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab"}, - {file = "pydantic_core-2.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4"}, - {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe"}, - {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d"}, - {file = "pydantic_core-2.18.3-cp311-none-win32.whl", hash = "sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7"}, - {file = "pydantic_core-2.18.3-cp311-none-win_amd64.whl", hash = "sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7"}, - {file = "pydantic_core-2.18.3-cp311-none-win_arm64.whl", hash = "sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4"}, - {file = "pydantic_core-2.18.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f"}, - {file = "pydantic_core-2.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022"}, - {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd"}, - {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be"}, - {file = "pydantic_core-2.18.3-cp312-none-win32.whl", hash = "sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"}, - {file = "pydantic_core-2.18.3-cp312-none-win_amd64.whl", hash = "sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6"}, - {file = "pydantic_core-2.18.3-cp312-none-win_arm64.whl", hash = "sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417"}, - {file = "pydantic_core-2.18.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522"}, - {file = "pydantic_core-2.18.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc"}, - {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4"}, - {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0"}, - {file = "pydantic_core-2.18.3-cp38-none-win32.whl", hash = "sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558"}, - {file = "pydantic_core-2.18.3-cp38-none-win_amd64.whl", hash = "sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc"}, - {file = "pydantic_core-2.18.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083"}, - {file = "pydantic_core-2.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06"}, - {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6"}, - {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af"}, - {file = "pydantic_core-2.18.3-cp39-none-win32.whl", hash = "sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78"}, - {file = "pydantic_core-2.18.3-cp39-none-win_amd64.whl", hash = "sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a"}, - {file = "pydantic_core-2.18.3.tar.gz", hash = "sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, + {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, + {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, + {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, + {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, + {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, + {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, + {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, + {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, + {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, + {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, + {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, + {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, + {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, ] [package.dependencies] @@ -2268,7 +2265,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2276,16 +2272,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2302,7 +2290,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2310,7 +2297,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2562,13 +2548,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2861,18 +2847,18 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.19.1" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, - {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] askar = ["anoncreds", "aries-askar", "indy-credx", "indy-vdr"] From 393a7e8cdaef96fe3ba97798dd9e4d7dfc03874f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 17:49:40 +0000 Subject: [PATCH 20/31] chore(deps): Bump dawidd6/action-download-artifact Bumps the all-actions group with 1 update: [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact). Updates `dawidd6/action-download-artifact` from 3 to 5 - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v3...v5) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/sonar-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml index 5f57b2d750..184f880925 100644 --- a/.github/workflows/sonar-pr.yml +++ b/.github/workflows/sonar-pr.yml @@ -15,7 +15,7 @@ jobs: with: fetch-depth: 0 - name: Download PR number artifact - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v5 with: workflow: Tests run_id: ${{ github.event.workflow_run.id }} @@ -26,7 +26,7 @@ jobs: with: path: ./PR_NUMBER - name: Download Test Coverage - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v5 with: workflow: Tests run_id: ${{ github.event.workflow_run.id }} From f927b21b31fc9e8a6364cb09c62095c58df2d78c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:05:23 -0700 Subject: [PATCH 21/31] chore(deps): Bump uuid-utils from 0.7.0 to 0.8.0 (#3034) Bumps [uuid-utils](https://github.com/aminalaee/uuid-utils) from 0.7.0 to 0.8.0. - [Release notes](https://github.com/aminalaee/uuid-utils/releases) - [Commits](https://github.com/aminalaee/uuid-utils/compare/0.7.0...0.8.0) --- updated-dependencies: - dependency-name: uuid-utils dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 189 +++++++++++++++++++++++++++---------------------- pyproject.toml | 2 +- 2 files changed, 104 insertions(+), 87 deletions(-) diff --git a/poetry.lock b/poetry.lock index a3d9b0b08f..421dff1209 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -1009,6 +1009,8 @@ files = [ {file = "frozendict-2.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d13b4310db337f4d2103867c5a05090b22bc4d50ca842093779ef541ea9c9eea"}, {file = "frozendict-2.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:b3b967d5065872e27b06f785a80c0ed0a45d1f7c9b85223da05358e734d858ca"}, {file = "frozendict-2.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:4ae8d05c8d0b6134bfb6bfb369d5fa0c4df21eabb5ca7f645af95fdc6689678e"}, + {file = "frozendict-2.4.4-py311-none-any.whl", hash = "sha256:705efca8d74d3facbb6ace80ab3afdd28eb8a237bfb4063ed89996b024bc443d"}, + {file = "frozendict-2.4.4-py312-none-any.whl", hash = "sha256:d9647563e76adb05b7cde2172403123380871360a114f546b4ae1704510801e5"}, {file = "frozendict-2.4.4.tar.gz", hash = "sha256:3f7c031b26e4ee6a3f786ceb5e3abf1181c4ade92dce1f847da26ea2c96008c7"}, ] @@ -1328,9 +1330,13 @@ files = [ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, @@ -2265,6 +2271,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2272,8 +2279,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2290,6 +2305,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2297,6 +2313,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2599,94 +2616,94 @@ files = [ [[package]] name = "uuid-utils" -version = "0.7.0" +version = "0.8.0" description = "Drop-in replacement for Python UUID in Rust" optional = false python-versions = ">=3.8" files = [ - {file = "uuid_utils-0.7.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1813869ffbf82ebe5fbe749cf0d5e580c605b0fd65d5e738e44439578280f993"}, - {file = "uuid_utils-0.7.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:afb6d3cea6f8b1d9692a1c5d7a93aa6189f973509ea272f4c070399e88cea36b"}, - {file = "uuid_utils-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38af087e1804774f563ff5f9f043022274dfce110b721ca272f89c0de4ee44e1"}, - {file = "uuid_utils-0.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:183603176b65401492db51a16526360997c91e32bc1ffe20ee527337fc57f634"}, - {file = "uuid_utils-0.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cebc0e99853c6c12f42e509c27af6131ef36b29e6f381d53c6d81eb1bd21a5f4"}, - {file = "uuid_utils-0.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49e0a42bd9c3825f10d38dcc49bafe5b6543b6c107e4b614e96abf8a7cd58a6f"}, - {file = "uuid_utils-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0f978aa8a51ca05142e4e81767d67de08b35ce7db28bc2e600d0c317472013"}, - {file = "uuid_utils-0.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3d2d02868c73334e84d80a7ad60e6c7506c72c059508e9a38db453e4110a652"}, - {file = "uuid_utils-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:03f710c032d903f273c720dfc080b68fead1ed543de8ad53c4c8dde64c6edd56"}, - {file = "uuid_utils-0.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b60c49becd9ff3844fe6e0e87319df9c84dd65bb86c36ad3514981f64e7a737a"}, - {file = "uuid_utils-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7ae618dbe27eb5c681a09bec4554d8da8264130a083657fcb80033bbf1c6114"}, - {file = "uuid_utils-0.7.0-cp310-none-win32.whl", hash = "sha256:fb73e36a209c2b585e878748615c0410d2422908ad86fc12b5ae66fedd7e326d"}, - {file = "uuid_utils-0.7.0-cp310-none-win_amd64.whl", hash = "sha256:8e30075e257184328356436a8a6b0e5a0c2b097c224a1e7f9d98a4c350ae5f21"}, - {file = "uuid_utils-0.7.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ca41e673b807405c0c5aa97ff8959b80884734b1eb55428c7285de245aa3e101"}, - {file = "uuid_utils-0.7.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cac7e2cf5b40ef297a998fc3ede146f171f99b18210e1237f01002c7e3fa6b0b"}, - {file = "uuid_utils-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bad486bcb3b1bd1f6a6e02d9627c51b993305bd2efd3eb4acd0aff529cd7d43"}, - {file = "uuid_utils-0.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd9d769f85bd24a558e8d1aee93400811e3f734199acc5410617f67b1041e0f4"}, - {file = "uuid_utils-0.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c99930f6d51efd15b6c2feb73b386bffccfc82c535eb7d8229e4fb6467f5c6c"}, - {file = "uuid_utils-0.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c68ba81b63e23032beda93eeab084f76f141017a26cb895c65777cf3c6c3474"}, - {file = "uuid_utils-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaa67667584aba2096292607e2f2e4485df1d1fb2594b2390227cf18df057f0"}, - {file = "uuid_utils-0.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6506fedaacd814b50cb62745b058796612c0ddd818a35a70082ea76f8b484931"}, - {file = "uuid_utils-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eaa55deae8fd4e7ff30a31f1661e953d70705efa3b09d0fc33576a8eaa589910"}, - {file = "uuid_utils-0.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0d4e7cd2f45e9a3dd371abb8532c6fcbb9befa1551522336095b02369e9144a9"}, - {file = "uuid_utils-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6a35a2205318cff201e76cbc6ad428c58e4d9d9ce9c83fd600c5295538be60e"}, - {file = "uuid_utils-0.7.0-cp311-none-win32.whl", hash = "sha256:a7c82f88158f0693cfbc769536d7c09a7cd3c58b22a1b2a041374db1ba03e2d3"}, - {file = "uuid_utils-0.7.0-cp311-none-win_amd64.whl", hash = "sha256:df8f82270295726d1f7d1e26026c29d33a2b40e6dcf8723cf7f5809909eaf6d6"}, - {file = "uuid_utils-0.7.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:53e5d6703f6a38aa1ba59cf8ac0486ac9a847e816e638cf9d6a2a4da4e9f6247"}, - {file = "uuid_utils-0.7.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c29183a8baedb39fc89e3d98ed2427d49e97ff3680f6832bffe73568d594970d"}, - {file = "uuid_utils-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:253fd6e8962008484e02fd4ff4a77ffbddd3867c0c3c24a6919eb4fefc3a2297"}, - {file = "uuid_utils-0.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de53537159212608eb15d4948d0e0098d2fa2b30d453f93d83fe737f0fd7188b"}, - {file = "uuid_utils-0.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:116c4b2ff774ce552324b196a3222302a2e78479a301fdb11c2aa1d294ab0f4d"}, - {file = "uuid_utils-0.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2eafb4fe02270e22a3bdb03c2107604cf68589a965667cabb71789beed318497"}, - {file = "uuid_utils-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ab7a012a1514e498f3f537852257ad2ec9402d1cc165865108dc6d9496bbd4"}, - {file = "uuid_utils-0.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08d58f7de04f3c43a4da05eece58002f4028a7275775ad5013e010abd51d7238"}, - {file = "uuid_utils-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e349d43a969f696dbc7acd002b64952b71674eaf948043a4c6dd1ab65d7c462"}, - {file = "uuid_utils-0.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:53f4c96e7fd1dab33dd56a885d9cffb5aaf21a9064115743e2cee1ff03cb359b"}, - {file = "uuid_utils-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c145629c4e48cda275310955632a8231c031f5e9b2eb93b9ab8a081dc6ab6681"}, - {file = "uuid_utils-0.7.0-cp312-none-win_amd64.whl", hash = "sha256:2ca368440148049475ff94f62d5011c34cd7954fe36247698fc05658d04ad9a1"}, - {file = "uuid_utils-0.7.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:48ed8e59c6fdcc8f825e9fa58afc7f98ba37f744a401ff28a47e7042a761b373"}, - {file = "uuid_utils-0.7.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:bb2777eb2837fc88aceb09addb45bfc7bc8dd0058d19627867b459dac3101a4b"}, - {file = "uuid_utils-0.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:070254d2435e9f187e0e8c0626fc6ed108d308cdec669c6d1493dd117bfbedd1"}, - {file = "uuid_utils-0.7.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:424abbbf7e8bdfe78ab552d838efeb9fd033cfe2208f00aadee2704169a1ebad"}, - {file = "uuid_utils-0.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:884a72b5f87f7534b685382221d872058bb743294cdb0f2215056b6cc85350fb"}, - {file = "uuid_utils-0.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab1509e21c74feb68b4a3e309bde8c64a8fce2e4552b79cb14058d6bc17a6129"}, - {file = "uuid_utils-0.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e6d70efc5e3449f0be3184a6925d0feb29fe40bdcd24ee2611a9021ee9b2580"}, - {file = "uuid_utils-0.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:411e29b3a2de713c4a3f3edc653599fb17ef3f38b6a788fecef62c3f229b7b0e"}, - {file = "uuid_utils-0.7.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3bf10bd5a898d72f50183718ca18bd61b8830c9134469b4d7b9f73f176f06c9f"}, - {file = "uuid_utils-0.7.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:247af7258004f497ec927fcf463914df5447eb691d7e9c23528280c471d6e830"}, - {file = "uuid_utils-0.7.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:01f7c73860b3cef024f9f57515dae5d52a554c3d2480d8410174ec5b609e20f5"}, - {file = "uuid_utils-0.7.0-cp38-none-win32.whl", hash = "sha256:d90d432c85bb2d9b3d67c8483b1134cf4363a39fa3273b8f05dcfde2bdddfc5d"}, - {file = "uuid_utils-0.7.0-cp38-none-win_amd64.whl", hash = "sha256:d31ebe0e6d5d1210da259de4d04ee31dfd5407296302bc2dfcca941e3e8f7bee"}, - {file = "uuid_utils-0.7.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:076fe5f6e5295a5d47b240ece6047d25ce15e8a114f60acc51b4025c3b973ed9"}, - {file = "uuid_utils-0.7.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:997f4d4f505391b69373c852662b5fe0af8c17b71fe401fea7687261464b9aa5"}, - {file = "uuid_utils-0.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59fc7ce3dddb5694f6ecd427d557a342f44075cdaf836cd99033fd0cc500e592"}, - {file = "uuid_utils-0.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:463b98c24c5f6f4d0b46174c1068c19007fe6414c38fbd58d5cb6c8d29cdd1ef"}, - {file = "uuid_utils-0.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65c5d33fd056517d0ab1624168359371b012cc6e3a0fd6029d212d3973032e90"}, - {file = "uuid_utils-0.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da47c5c4348a5f88749ac8fd54715bdfa18c1317ebf709121721e9b5fb338c66"}, - {file = "uuid_utils-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04f39fd90656770422cc7ec46467c2eb758e19d70c5844770bd67834ebae40ea"}, - {file = "uuid_utils-0.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5817e38d497ae643c68044c5c84153fa47557df1f8c1661c17bd1e26bda1058"}, - {file = "uuid_utils-0.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:407c15bbde425bc4df829771ef601260eda8617ac5adc6f1eb924d916674c34f"}, - {file = "uuid_utils-0.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d4ac00e7f3bbb578e20fadf81468f28b63d1b29930192d8285e9d01b2f75f270"}, - {file = "uuid_utils-0.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d937d37b696a2e3346171367a6ecf69519af4f2a5325e8e7f9a7cfb61597387"}, - {file = "uuid_utils-0.7.0-cp39-none-win32.whl", hash = "sha256:a4fd826bc2c260716b53db90b2e4c8a0f752aae053fbfbd1860e6e450bcf6ae9"}, - {file = "uuid_utils-0.7.0-cp39-none-win_amd64.whl", hash = "sha256:c1aa084a1b4842c49526ed1189122a96a8cdd73f66ef4219956279044bf6721f"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:15eb3621d24fb6aab7f8e7b315356171795ca0f226ba9c31490fb9c08712c201"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bd0dac47317dcdefafe493428237019582ba8adb91c3ec80e033ee631c173f6d"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7b555e485f17ab1ab0cb963ff48c6404b93dd491aef7f52a8ae8c52f7f51841"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e66bddd9a469645ede16f0abde5db4dd1a75bc9628ab0b68cad0b848de8494aa"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ad6957427be8f2e48d2f128b3382b3c8e33b4b26542d757e5957c9593773082"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c753f5b690a481d31f13668a57610a4ee9805d0bd4515ab74a3766bea3b0e66"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e99615eb01550e1f883b5b251a04e8afe053dd30fb6c1af823bd14841bd9290"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cb241970c10cccd37ecac5b3759276ca499cb5b639b832167f91b0a98383e89d"}, - {file = "uuid_utils-0.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:14b672b950e792545fde222cf08f9ba9e30ac69399c2ca34b91d4fa457ce1528"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1229b9849a239714899040f8af9c7b3b7ad790483ac0bdf06982eb03383e7a93"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2b6c56101e5dedf06c81c5f3e3dc9d542feb4a5443b01a100c14eef6ae7e9ec4"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77c7f5e54fad8d761e019122080b14fae9568dd09cbb908f349284efa8f9a792"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b429a906f0dff1c35d55ca17c5f7fedf3149cb405808b43ba4f3a6d21732c31"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06608c7d643149dee92ceebc73a84bb736d4394f200ecb794541a79e10bc482d"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:655e505c4e7c321e7f60572fdd594bdfdd96556a9699f697045e3d0b4699f30a"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1e59447b45d5988572e450f43de5546e1d2f6643d2e0137d83b5fdad204fd05"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3010bdaff5c2a78980849aa6b082e7a0013949c8e4d317934f4aaacf14a2d22"}, - {file = "uuid_utils-0.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ec25fadeeb34c41ef95a8b849a3e4dcc39e96eb39367323ba873bc1732d6516a"}, - {file = "uuid_utils-0.7.0.tar.gz", hash = "sha256:015aa22711ffd57c5001c2477c6a40121db2794ae3be181a0bf79eef80e28943"}, + {file = "uuid_utils-0.8.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:79c5cb8e8edafe018d818b6ae702a1cc58c77177f48a6c52138293c383072a11"}, + {file = "uuid_utils-0.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:df9db6fc543854266cd21cc232b6021f49b062be1e1fedce6943ef073f767c9d"}, + {file = "uuid_utils-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:235508055a1ecc17fa9e4623d36e70d9d5769b36dbe4fa63939535c4bfddb0d3"}, + {file = "uuid_utils-0.8.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:761bdec7e9dba866ef0ba6e3cf1998566a48929d08f450ee20c8a26cd4d81e75"}, + {file = "uuid_utils-0.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8c11d0bff47b43d556377862877f61d2cb239fdfdcb594a25763a84254de8f3"}, + {file = "uuid_utils-0.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8c84c3f4f43b9242327e955696b7760b153112eafe3e168cefc98513b6c001b"}, + {file = "uuid_utils-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93bf94e643ef57254a2e68fda5ba1019b55841ac96922f3a316d4207390859b"}, + {file = "uuid_utils-0.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7f3a3f6f8a35b2d5dd369e1300970424dd4169d6da1c7fe6225047e16dbbc6af"}, + {file = "uuid_utils-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:752ae76423a77da3eafcce1cd0e0b2065d199245dedb53d06821cfdc772aee21"}, + {file = "uuid_utils-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a7bc630afb8e6173b92b900d5648c396bf1c23570f13f9283f846e88f13ddfda"}, + {file = "uuid_utils-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6277de886cf525f41b5092091a514c08664a1d50f5103e077afeb9b256287625"}, + {file = "uuid_utils-0.8.0-cp310-none-win32.whl", hash = "sha256:fa5595b8cb49cb950d567e1a4b9e3cc709061cb8bad91102c3331b84a440c82c"}, + {file = "uuid_utils-0.8.0-cp310-none-win_amd64.whl", hash = "sha256:ab65787780ffc66e65590025b7da4876e7434086729e9809dfffcbee8cc865e4"}, + {file = "uuid_utils-0.8.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3659dbfbb02e0992fcc8b3e569e8fdaf7c6a9c557641cd759ee0ca0750f43a3c"}, + {file = "uuid_utils-0.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4f88d378046712158c6b2bae759a79b9d423d6ef31471e7d1fc479171b948b53"}, + {file = "uuid_utils-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3d860656a11ad3e12b81dc3529cbba0991188a38b8aac364b02be0bfe0c3d6a"}, + {file = "uuid_utils-0.8.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:248a62fdb3f8ca7b6b16978c92e1923252ed3910040154cfdbb891460aa4dc90"}, + {file = "uuid_utils-0.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49993e9a0157ecfc3751785461048515ff5fed8cd2e6fa74c91c469157f9a79c"}, + {file = "uuid_utils-0.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a5d1268fadb2c2d06a44f6394cf251e0237136a59a85cfc957587ae7b5355d1"}, + {file = "uuid_utils-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5acda5a1c10a69cfa8fe652bbe916af8b7bbb1eec495e3040f435ecc13f1180b"}, + {file = "uuid_utils-0.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:78c17a0d51918ffb4b5f3934ab84a19dc340743da7a2db4257d425328eb35df8"}, + {file = "uuid_utils-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d30c95a5f50031c9d7e220ce51dd48c7ae8d044414715ad1842c41a35a2b004f"}, + {file = "uuid_utils-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a6eaa3a03c6d219b79501c096cd06f08a91f9a707a1f380a96acc7f9f1620ba3"}, + {file = "uuid_utils-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98dc793004cd69c36e03a0654c0b288719ab0545fbbd59274511e3d25dcf6725"}, + {file = "uuid_utils-0.8.0-cp311-none-win32.whl", hash = "sha256:b9c51ca726721bd9532180edaeea57026fdfeaacaa5a7c72fae61865ffe16fda"}, + {file = "uuid_utils-0.8.0-cp311-none-win_amd64.whl", hash = "sha256:f5fe73a1d6ca1b586881a91178a946c6b63509d705999850102aa3a7dea584bf"}, + {file = "uuid_utils-0.8.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9a1fe530c0762ad781470ba20464ebaf15b2263b49b74361798153e9fab4db60"}, + {file = "uuid_utils-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7bcd38931b35ffbeb1bb5292be72c74f41a51b2a6023093c7203ded5a257956a"}, + {file = "uuid_utils-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d29f8fdfcdbc97db39d45df8399161738a5b4fae73b9458bce76d6f927b88499"}, + {file = "uuid_utils-0.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842645f00a4883edca9b53908aad57d9658cabf4837b25e3578eaae5b6935eed"}, + {file = "uuid_utils-0.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0860b1f3148d1bffe3321cd7f1fa60c202a852bf6d375f453431c1edc3593612"}, + {file = "uuid_utils-0.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d99e4b2e294c71cefa92217c14848b186898493a96e1103e4e54c13827e005ea"}, + {file = "uuid_utils-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8c4a7208070251e85df64bc428d464ec46da599b709ec1115692b706d3b8f38"}, + {file = "uuid_utils-0.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d9496e77aea6e98bddfee52f72b915c02e91dd2462474eb0ddd47028e3235a0c"}, + {file = "uuid_utils-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8028b3604a3493d4df2b1e2fdc0def0678805680ed6a66bf97c3ed84b25524ae"}, + {file = "uuid_utils-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0248cedc45b9cf1ebad1e2887a30d209aba21f6e0a41571a1f22597d1349333"}, + {file = "uuid_utils-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:989dd47a5e383af9b72fa5d74f23c16276e95f1d4f2bb40240d4bf9e7334741f"}, + {file = "uuid_utils-0.8.0-cp312-none-win_amd64.whl", hash = "sha256:1594b8f22067ca080708e6fca13c94e62285ce03c0977841d9f1e5192b66335e"}, + {file = "uuid_utils-0.8.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:56b27315d37daf74720f39956c5d77417ee0a7944fdc58b44a9094dd8207b3d6"}, + {file = "uuid_utils-0.8.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:e0f681c5085536cb3d0353aeb945a4fb31c9bb6cfabbd8a13a6d849f47107eb6"}, + {file = "uuid_utils-0.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15eeddfe03c6e561fe25f90899b46836999fa6af2c600bab79d03815ea579424"}, + {file = "uuid_utils-0.8.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ac8eb55dd01b456b94ffb366e7ea763c3f107c33fafae86309b6e293575e2b8"}, + {file = "uuid_utils-0.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:689970e152c1b21a4da1d862573a41ae130f2271d8f7606ccb1527bf33931b2d"}, + {file = "uuid_utils-0.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d6b6235f57e6d47fe17dcda1a0d8b7cc936b7d0a7ddce6955940add752a6300"}, + {file = "uuid_utils-0.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b596f25955d7c7fcaadf7ede28b51bd7f69cf4040038fff241e7c83dede685"}, + {file = "uuid_utils-0.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:062d841060d2f466fc1db9221f93fc5b031d1453592118e0e936fd9dddb5c70d"}, + {file = "uuid_utils-0.8.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ac187ffbc1cd105d056ed901f13b78f0fc9b5cf2d7072103181aacb2f1a0d036"}, + {file = "uuid_utils-0.8.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d54b03d9c9186dff5b24cf93f329180ac4b690274273b4b2b68b8288aaa31dde"}, + {file = "uuid_utils-0.8.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:1295f50a42b230de1bc5abbf5a35bfd2e7e52b3952081e6bc5cdbb21697cd10e"}, + {file = "uuid_utils-0.8.0-cp38-none-win32.whl", hash = "sha256:73ac2e8ec22fca96f2150e92ae657e115b14d9328326835b560cef02b04e737a"}, + {file = "uuid_utils-0.8.0-cp38-none-win_amd64.whl", hash = "sha256:17509f604299da096dfadac58b99844d53ba99a1564a3f0527a8eb040e583661"}, + {file = "uuid_utils-0.8.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:16ed8988aac8ac52e934dcd1dac5992f4f6ef530469c30b7571adcc203848deb"}, + {file = "uuid_utils-0.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:73638f2b70d3d00470c034da2268f86b261d61c4b99256b536c373d7defca744"}, + {file = "uuid_utils-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba9086cff89196017218edc9156fe8b1b9cc22714b37862860aba6f48fa7e1d"}, + {file = "uuid_utils-0.8.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894ab37a18e12dfb703079373606e3068d5be97b8bce8658d2505087b5f5db09"}, + {file = "uuid_utils-0.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40646f7152d57028e2c807911bdea898a1e2f110859502f31fe8f8ccb954586"}, + {file = "uuid_utils-0.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b6b360edea234a0de1a892e7a96f4189b14619a9389c338a83a14185d0e2f67"}, + {file = "uuid_utils-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c09788d5ea000836199033075930108ec417c71f3a7c141ede349b7a831f6287"}, + {file = "uuid_utils-0.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ce326b04bf712d3ad734339b3573470a4fda2474065222dad08528ba3f3ad0b"}, + {file = "uuid_utils-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d2610935ca6681e26faa1045ecafc0d5ee22f525eed0f993e71dcf9b5bb703f3"}, + {file = "uuid_utils-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d665faa544349571a1bb158592a9527c3d7ec17d4baf1de2ef4518d43e04f0e6"}, + {file = "uuid_utils-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d37791159173b9e63f6cbbce5470bbef129b28504f176f433e2435504854d06d"}, + {file = "uuid_utils-0.8.0-cp39-none-win32.whl", hash = "sha256:536620e5eb89ef4a75c78e7f60fa28321c06af371310e3f25d27e6930bd81ca2"}, + {file = "uuid_utils-0.8.0-cp39-none-win_amd64.whl", hash = "sha256:516f4e63efdacc1e6fea07cc7e2cb2e98579a56bba24d2d32cb50cf8a3ff82d3"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0859c5b907445d8304baf381b3985f72fba366597eed9315cd0ae6d21998d8e0"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d8544f800e513aa9bf9c9a9df56a218a8a050ffd109ec7a58f86c5decb7e40fa"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfde725effb4b09ed871a3685bd4bcbacda99a612e43ef65c9fc33dc07c5270c"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59fc6ba8bcd653af28e805c65b7d7e623000bafdfcbbd2f7483556e012d7dbc2"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc4d254841950ad33f26ce5fa043a32add0cef5ac96475062d6e23aa76f91d70"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd30365435561390db02576ee3a6ff9896a0fc4c5c73dcf3d02a500043776fc4"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62a210eb875e480a2cd843a37719e29853350e87904672fb66b6798eb4d140b7"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07c9cb3c467ed710c9f3bfbaeb2d2594012a661d42d5e5b63e228ad80d4c4eaf"}, + {file = "uuid_utils-0.8.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2c414f2cc72e0139f4acff3ab6936a61bc5e37dff42cb66b07948dcd5d646c85"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:91e34a233b0213f9871a2f91c2398794a0a8c08b749936f77c4ddb466402aceb"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ec4e5b9e60d7a602b3e9dcd058fe9ae3d045af02b4cb2d3a125311de7dbc8e8b"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e2bf7536ea1fc7492b94da7d081c23363047aac167a453df69498fe9bdf5a1"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea2754b941addb4ef04c958aa4c21d2d6526cd2003d543f9dd6559e5f937a5d6"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb8049d535f8d8da07ee31842a90543b616fd662afb74680cabdb7cc338e7cc7"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a23ff19249d48c0e0203305348390f12e531f52986a043d095ffc9f9e605cbc"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cadf3de8d588b72a63d91a0b4dac8f0470337f09e94ae7bd2c9a09913f2b886e"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81e76bbd330c45398f42d3f3550d94af3d01a81ff8d95c8f8a07a27e825debb"}, + {file = "uuid_utils-0.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c85809d454b3695d70d9695b14c44e7509e3e0fd748d59f7ff8491731c24c9fd"}, + {file = "uuid_utils-0.8.0.tar.gz", hash = "sha256:f9049dd91045bb99bdbe05976faf822d1a6ddb7a4b699ba55eff656750d95ee3"}, ] [[package]] @@ -2867,4 +2884,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "e23d53b1e134b975a054cd296099b7f00a2c0850941e1ca80fdfb85d60aa1fde" +content-hash = "5aae67738c26befd98f37fea3c5401fcab4dbf6c9c69f1fc5764dbf3518d3df0" diff --git a/pyproject.toml b/pyproject.toml index f2579a2b1f..6545251492 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ unflatten="~0.1" sd-jwt = "^0.10.3" did-peer-2 = "^0.1.2" did-peer-4 = "^0.1.4" -uuid_utils = "^0.7.0" +uuid_utils = "^0.8.0" # askar aries-askar= { version = "~0.3.0", optional = true } From 61066575f177731ee090665529a2ee9b71701119 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:07:12 -0700 Subject: [PATCH 22/31] chore(deps): Bump configargparse from 1.5.5 to 1.7 (#3035) Bumps [configargparse](https://github.com/bw2/ConfigArgParse) from 1.5.5 to 1.7. - [Release notes](https://github.com/bw2/ConfigArgParse/releases) - [Commits](https://github.com/bw2/ConfigArgParse/compare/1.5.5...1.7) --- updated-dependencies: - dependency-name: configargparse dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 421dff1209..67d6681add 100644 --- a/poetry.lock +++ b/poetry.lock @@ -551,13 +551,13 @@ files = [ [[package]] name = "configargparse" -version = "1.5.5" +version = "1.7" description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" files = [ - {file = "ConfigArgParse-1.5.5-py3-none-any.whl", hash = "sha256:541360ddc1b15c517f95c0d02d1fca4591266628f3667acdc5d13dccc78884ca"}, - {file = "ConfigArgParse-1.5.5.tar.gz", hash = "sha256:363d80a6d35614bd446e2f2b1b216f3b33741d03ac6d0a92803306f40e555b58"}, + {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"}, + {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"}, ] [package.extras] @@ -2884,4 +2884,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "5aae67738c26befd98f37fea3c5401fcab4dbf6c9c69f1fc5764dbf3518d3df0" +content-hash = "6d8895caf68777182537454316499ca027668295de7595dad4a6dbc8ba0680a7" diff --git a/pyproject.toml b/pyproject.toml index 6545251492..8414889af7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ aiohttp-cors="~0.7.0" apispec="^6.6.0" async-timeout="~4.0.2" base58="~2.1.0" -ConfigArgParse="~1.5.3" +ConfigArgParse="~1.7" deepmerge="~0.3.0" ecdsa="~0.19.0" jsonpath-ng="1.6.1" From 21b6ba7685045e19491f75d296b3325e22406fe2 Mon Sep 17 00:00:00 2001 From: jamshale Date: Thu, 13 Jun 2024 18:54:32 +0000 Subject: [PATCH 23/31] Prevent getting stuck with no active registry Signed-off-by: jamshale --- .../v2_0/formats/indy/handler.py | 145 ++++++++++-------- aries_cloudagent/revocation/indy.py | 31 +++- .../models/issuer_rev_reg_record.py | 4 +- 3 files changed, 113 insertions(+), 67 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 998ae8947d..3b1e01f398 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -1,19 +1,19 @@ """V2.0 issue-credential indy credential format handler.""" +import asyncio +import json import logging +from typing import Mapping, Optional, Tuple from marshmallow import RAISE -import json -from typing import Mapping, Tuple -import asyncio from ......cache.base import BaseCache from ......core.profile import Profile -from ......indy.issuer import IndyIssuer, IndyIssuerRevocationRegistryFullError from ......indy.holder import IndyHolder, IndyHolderError +from ......indy.issuer import IndyIssuer, IndyIssuerRevocationRegistryFullError from ......indy.models.cred import IndyCredentialSchema -from ......indy.models.cred_request import IndyCredRequestSchema from ......indy.models.cred_abstract import IndyCredAbstractSchema +from ......indy.models.cred_request import IndyCredRequestSchema from ......ledger.base import BaseLedger from ......ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, @@ -30,7 +30,6 @@ from ......revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ......revocation.models.revocation_registry import RevocationRegistry from ......storage.base import BaseStorage - from ...message_types import ( ATTACHMENT_FORMAT, CRED_20_ISSUE, @@ -39,16 +38,14 @@ CRED_20_REQUEST, ) from ...messages.cred_format import V20CredFormat -from ...messages.cred_proposal import V20CredProposal +from ...messages.cred_issue import V20CredIssue from ...messages.cred_offer import V20CredOffer +from ...messages.cred_proposal import V20CredProposal from ...messages.cred_request import V20CredRequest -from ...messages.cred_issue import V20CredIssue from ...models.cred_ex_record import V20CredExRecord from ...models.detail.indy import V20CredExRecordIndy - -from ..handler import CredFormatAttachment, V20CredFormatError, V20CredFormatHandler from ..anoncreds.handler import AnonCredsCredFormatHandler - +from ..handler import CredFormatAttachment, V20CredFormatError, V20CredFormatHandler LOGGER = logging.getLogger(__name__) @@ -369,54 +366,18 @@ async def receive_request( "Indy issue credential format cannot start from credential request" ) - async def issue_credential( - self, cred_ex_record: V20CredExRecord, retries: int = 5 - ) -> CredFormatAttachment: - """Issue indy credential.""" - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return await self.anoncreds_handler.issue_credential( - cred_ex_record, retries - ) - - await self._check_uniqueness(cred_ex_record.cred_ex_id) - - cred_offer = cred_ex_record.cred_offer.attachment(IndyCredFormatHandler.format) - cred_request = cred_ex_record.cred_request.attachment( - IndyCredFormatHandler.format - ) - cred_values = cred_ex_record.cred_offer.credential_preview.attr_dict( - decode=False - ) - schema_id = cred_offer["schema_id"] - cred_def_id = cred_offer["cred_def_id"] - - issuer = self.profile.inject(IndyIssuer) - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - if multitenant_mgr: - ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) - else: - ledger_exec_inst = self.profile.inject(IndyLedgerRequestsExecutor) - ledger = ( - await ledger_exec_inst.get_ledger_for_identifier( - schema_id, - txn_record_type=GET_SCHEMA, - ) - )[1] - async with ledger: - schema = await ledger.get_schema(schema_id) - cred_def = await ledger.get_credential_definition(cred_def_id) - revocable = cred_def["value"].get("revocation") - result = None - - for attempt in range(max(retries, 1)): - if attempt > 0: - LOGGER.info( - "Waiting 2s before retrying credential issuance for cred def '%s'", - cred_def_id, - ) - await asyncio.sleep(2) - + async def _issue_credential_retry( + self, + retries: int, + cred_def_id: str, + schema: dict, + cred_offer: dict, + cred_request: dict, + cred_values: dict[str, str], + revocable: bool, + issuer: IndyIssuer, + ) -> tuple[Optional[dict], Optional[str], Optional[str]]: + for _ in range(max(retries, 1)): if revocable: revoc = IndyRevocation(self.profile) registry_info = await revoc.get_or_create_active_registry(cred_def_id) @@ -449,7 +410,71 @@ async def issue_credential( del revoc result = self.get_format_data(CRED_20_ISSUE, json.loads(cred_json)) - break + if result: + return result, rev_reg_id, cred_rev_id + + LOGGER.info( + "Waiting 2s before retrying credential issuance for cred def '%s'", + cred_def_id, + ) + await asyncio.sleep(2) + + return None, None, None + + async def _get_ledger_for_schema(self, schema_id: str): + multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) + else: + ledger_exec_inst = self.profile.inject(IndyLedgerRequestsExecutor) + return ( + await ledger_exec_inst.get_ledger_for_identifier( + schema_id, + txn_record_type=GET_SCHEMA, + ) + )[1] + + async def issue_credential( + self, cred_ex_record: V20CredExRecord, retries: int = 5 + ) -> CredFormatAttachment: + """Issue indy credential.""" + # Temporary shim while the new anoncreds library integration is in progress + if self.anoncreds_handler: + return await self.anoncreds_handler.issue_credential( + cred_ex_record, retries + ) + + await self._check_uniqueness(cred_ex_record.cred_ex_id) + + cred_offer = cred_ex_record.cred_offer.attachment(IndyCredFormatHandler.format) + cred_request = cred_ex_record.cred_request.attachment( + IndyCredFormatHandler.format + ) + cred_values = cred_ex_record.cred_offer.credential_preview.attr_dict( + decode=False + ) + schema_id = cred_offer["schema_id"] + cred_def_id = cred_offer["cred_def_id"] + + issuer = self.profile.inject(IndyIssuer) + ledger = await self._get_ledger_for_schema(schema_id) + + async with ledger: + schema = await ledger.get_schema(schema_id) + cred_def = await ledger.get_credential_definition(cred_def_id) + + revocable = True if cred_def["value"].get("revocation") else False + + result, rev_reg_id, cred_rev_id = await self._issue_credential_retry( + retries, + cred_def_id, + schema, + cred_offer, + cred_request, + cred_values, + revocable, + issuer, + ) if not result: raise V20CredFormatError( diff --git a/aries_cloudagent/revocation/indy.py b/aries_cloudagent/revocation/indy.py index bd0b8ced36..13a63e0844 100644 --- a/aries_cloudagent/revocation/indy.py +++ b/aries_cloudagent/revocation/indy.py @@ -222,7 +222,7 @@ async def get_issuer_rev_reg_delta( return rev_reg_delta async def get_or_create_active_registry( - self, cred_def_id: str, max_cred_num: int = None + self, cred_def_id: str ) -> Optional[Tuple[IssuerRevRegRecord, RevocationRegistry]]: """Fetch the active revocation registry. @@ -240,14 +240,35 @@ async def get_or_create_active_registry( pass async with self._profile.session() as session: - rev_reg_recs = await IssuerRevRegRecord.query_by_cred_def_id( - session, cred_def_id, {"$neq": IssuerRevRegRecord.STATE_FULL} + rev_reg_records = await IssuerRevRegRecord.query_by_cred_def_id( + session, cred_def_id ) - if not rev_reg_recs: + full_registries = [ + rev + for rev in rev_reg_records + if rev.state == IssuerRevRegRecord.STATE_FULL + ] + + # all registries are full, create a new one + if len(full_registries) == len(rev_reg_records): await self.init_issuer_registry( cred_def_id, - max_cred_num=max_cred_num, + max_cred_num=rev_reg_records[0].max_cred_num, + ) + # if there is a posted registry, activate oldest + else: + posted_registries = sorted( + [ + rev + for rev in rev_reg_records + if rev.state == IssuerRevRegRecord.STATE_POSTED + ] ) + if posted_registries: + await self._set_registry_status( + posted_registries[0].revoc_reg_id, + IssuerRevRegRecord.STATE_ACTIVE, + ) return None async def get_ledger_registry(self, revoc_reg_id: str) -> RevocationRegistry: diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 34e2d53415..973ab41b66 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -524,7 +524,7 @@ def get_registry(self) -> RevocationRegistry: @classmethod async def query_by_cred_def_id( - cls, session: ProfileSession, cred_def_id: str, state: str = None + cls, session: ProfileSession, cred_def_id: str, state: str = None, limit=None ) -> Sequence["IssuerRevRegRecord"]: """Retrieve issuer revocation registry records by credential definition ID. @@ -539,7 +539,7 @@ async def query_by_cred_def_id( (("cred_def_id", cred_def_id), ("state", state)), ) ) - return await cls.query(session, tag_filter) + return await cls.query(session, tag_filter, limit=limit) @classmethod async def query_by_pending( From a3631848f483c027ec725f50a45c1efdb8563b13 Mon Sep 17 00:00:00 2001 From: jamshale Date: Mon, 17 Jun 2024 19:45:51 +0000 Subject: [PATCH 24/31] Refactor with more efficient queries Signed-off-by: jamshale --- aries_cloudagent/revocation/indy.py | 30 +++++++++---------- .../models/issuer_rev_reg_record.py | 15 ++++++++-- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/aries_cloudagent/revocation/indy.py b/aries_cloudagent/revocation/indy.py index 13a63e0844..3e36f4fac4 100644 --- a/aries_cloudagent/revocation/indy.py +++ b/aries_cloudagent/revocation/indy.py @@ -240,31 +240,31 @@ async def get_or_create_active_registry( pass async with self._profile.session() as session: - rev_reg_records = await IssuerRevRegRecord.query_by_cred_def_id( - session, cred_def_id + full_registries = await IssuerRevRegRecord.query_by_cred_def_id( + session, cred_def_id, None, IssuerRevRegRecord.STATE_FULL, 1 ) - full_registries = [ - rev - for rev in rev_reg_records - if rev.state == IssuerRevRegRecord.STATE_FULL - ] # all registries are full, create a new one - if len(full_registries) == len(rev_reg_records): + if not full_registries: + # Use any registry to get max cred num + any_registry = ( + await IssuerRevRegRecord.query_by_cred_def_id( + session, cred_def_id, limit=1 + ) + )[0] await self.init_issuer_registry( cred_def_id, - max_cred_num=rev_reg_records[0].max_cred_num, + max_cred_num=any_registry.max_cred_num, ) # if there is a posted registry, activate oldest else: - posted_registries = sorted( - [ - rev - for rev in rev_reg_records - if rev.state == IssuerRevRegRecord.STATE_POSTED - ] + posted_registries = await IssuerRevRegRecord.query_by_cred_def_id( + session, cred_def_id, IssuerRevRegRecord.STATE_POSTED, None, None ) if posted_registries: + posted_registries = sorted( + posted_registries, key=lambda r: r.created_at + ) await self._set_registry_status( posted_registries[0].revoc_reg_id, IssuerRevRegRecord.STATE_ACTIVE, diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 973ab41b66..174c950324 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -524,7 +524,12 @@ def get_registry(self) -> RevocationRegistry: @classmethod async def query_by_cred_def_id( - cls, session: ProfileSession, cred_def_id: str, state: str = None, limit=None + cls, + session: ProfileSession, + cred_def_id: str, + state: str = None, + negative_state: str = None, + limit=None, ) -> Sequence["IssuerRevRegRecord"]: """Retrieve issuer revocation registry records by credential definition ID. @@ -539,7 +544,13 @@ async def query_by_cred_def_id( (("cred_def_id", cred_def_id), ("state", state)), ) ) - return await cls.query(session, tag_filter, limit=limit) + return await cls.query( + session, + tag_filter, + post_filter_positive={"state": state} if state else None, + post_filter_negative={"state": negative_state} if negative_state else None, + limit=limit, + ) @classmethod async def query_by_pending( From f8e82f8ed00e002ae19d3aedae47d0e00cbeabc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:34:33 -0700 Subject: [PATCH 25/31] chore(deps): Bump mkdocs-material from 9.5.10 to 9.5.27 (#3036) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.10 to 9.5.27. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.10...9.5.27) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mkdocs-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt index a64b578cf6..1ea33417b0 100644 --- a/mkdocs-requirements.txt +++ b/mkdocs-requirements.txt @@ -1,3 +1,3 @@ -mkdocs-material==9.5.10 +mkdocs-material==9.5.27 mike==2.0.0 From c4308a5f7b126f70e31af1e15ece09b25ca7a2fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:06:52 -0700 Subject: [PATCH 26/31] chore(deps): Bump packaging from 23.1 to 23.2 (#3037) Bumps [packaging](https://github.com/pypa/packaging) from 23.1 to 23.2. - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/23.1...23.2) --- updated-dependencies: - dependency-name: packaging dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 67d6681add..89ef76b8a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1657,13 +1657,13 @@ files = [ [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -2884,4 +2884,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "6d8895caf68777182537454316499ca027668295de7595dad4a6dbc8ba0680a7" +content-hash = "91116c343135f25794b717d1afb2870d7f230adea0746df19bb9098c1e995d04" diff --git a/pyproject.toml b/pyproject.toml index 8414889af7..8138372dbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ Markdown="~3.6" markupsafe="2.0.1" marshmallow="~3.20.1" nest_asyncio="~1.6.0" -packaging="~23.1" +packaging="~23.2" portalocker="~2.8.2" prompt_toolkit=">=2.0.9,<2.1.0" pydid="^0.5.1" From 9d39d15ded528c4178cb4eb8ba3412b412c53a48 Mon Sep 17 00:00:00 2001 From: jamshale Date: Mon, 17 Jun 2024 22:15:22 +0000 Subject: [PATCH 27/31] Add unit tests Signed-off-by: jamshale --- aries_cloudagent/revocation/indy.py | 4 +- .../revocation/tests/test_indy.py | 73 ++++++++++++++++++- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/revocation/indy.py b/aries_cloudagent/revocation/indy.py index 3e36f4fac4..f22b4fefb3 100644 --- a/aries_cloudagent/revocation/indy.py +++ b/aries_cloudagent/revocation/indy.py @@ -266,8 +266,8 @@ async def get_or_create_active_registry( posted_registries, key=lambda r: r.created_at ) await self._set_registry_status( - posted_registries[0].revoc_reg_id, - IssuerRevRegRecord.STATE_ACTIVE, + revoc_reg_id=posted_registries[0].revoc_reg_id, + state=IssuerRevRegRecord.STATE_ACTIVE, ) return None diff --git a/aries_cloudagent/revocation/tests/test_indy.py b/aries_cloudagent/revocation/tests/test_indy.py index 256b80a04e..43eb0c4b81 100644 --- a/aries_cloudagent/revocation/tests/test_indy.py +++ b/aries_cloudagent/revocation/tests/test_indy.py @@ -1,6 +1,7 @@ -from aries_cloudagent.tests import mock from unittest import IsolatedAsyncioTestCase +from aries_cloudagent.tests import mock + from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -9,7 +10,6 @@ from ...multitenant.base import BaseMultitenantManager from ...multitenant.manager import MultitenantManager from ...storage.error import StorageNotFoundError - from ..error import ( RevocationNotSupportedError, RevocationRegistryBadSizeError, @@ -255,3 +255,72 @@ async def test_get_ledger_registry(self): mock_from_def.assert_called_once_with( self.ledger.get_revoc_reg_def.return_value, True ) + + @mock.patch( + "aries_cloudagent.revocation.indy.IndyRevocation.get_active_issuer_rev_reg_record", + mock.CoroutineMock( + return_value=mock.MagicMock( + get_registry=mock.MagicMock( + return_value=mock.MagicMock( + get_or_fetch_local_tails_path=mock.CoroutineMock( + return_value="dummy" + ) + ) + ) + ) + ), + ) + async def test_get_or_create_active_registry_has_active_registry(self, *_): + result = await self.revoc.get_or_create_active_registry("cred_def_id") + assert isinstance(result, tuple) + + @mock.patch( + "aries_cloudagent.revocation.indy.IndyRevocation.get_active_issuer_rev_reg_record", + mock.CoroutineMock(side_effect=StorageNotFoundError("No such record")), + ) + @mock.patch( + "aries_cloudagent.revocation.indy.IndyRevocation.init_issuer_registry", + mock.CoroutineMock(return_value=None), + ) + @mock.patch.object( + IssuerRevRegRecord, + "query_by_cred_def_id", + side_effect=[[], [IssuerRevRegRecord(max_cred_num=3)]], + ) + async def test_get_or_create_active_registry_has_no_active_and_only_full_registies( + self, *_ + ): + result = await self.revoc.get_or_create_active_registry("cred_def_id") + + assert not result + assert self.revoc.init_issuer_registry.call_args.kwargs["max_cred_num"] == 3 + + @mock.patch( + "aries_cloudagent.revocation.indy.IndyRevocation.get_active_issuer_rev_reg_record", + mock.CoroutineMock(side_effect=StorageNotFoundError("No such record")), + ) + @mock.patch( + "aries_cloudagent.revocation.indy.IndyRevocation._set_registry_status", + mock.CoroutineMock(return_value=None), + ) + @mock.patch.object( + IssuerRevRegRecord, + "query_by_cred_def_id", + side_effect=[ + [IssuerRevRegRecord(max_cred_num=3)], + [ + IssuerRevRegRecord( + revoc_reg_id="test-rev-reg-id", + state=IssuerRevRegRecord.STATE_POSTED, + ) + ], + ], + ) + async def test_get_or_create_active_registry_has_no_active_with_posted(self, *_): + result = await self.revoc.get_or_create_active_registry("cred_def_id") + + assert not result + assert ( + self.revoc._set_registry_status.call_args.kwargs["state"] + == IssuerRevRegRecord.STATE_ACTIVE + ) From 10f27b1689bcd68e1356ab42348cd7365b64506e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:16:15 -0700 Subject: [PATCH 28/31] chore(deps): Bump marshmallow from 3.20.2 to 3.21.3 (#3038) Bumps [marshmallow](https://github.com/marshmallow-code/marshmallow) from 3.20.2 to 3.21.3. - [Changelog](https://github.com/marshmallow-code/marshmallow/blob/dev/CHANGELOG.rst) - [Commits](https://github.com/marshmallow-code/marshmallow/compare/3.20.2...3.21.3) --- updated-dependencies: - dependency-name: marshmallow dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 13 ++++++------- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89ef76b8a1..eaa5130a3e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1505,22 +1505,21 @@ files = [ [[package]] name = "marshmallow" -version = "3.20.2" +version = "3.21.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, - {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["pre-commit (>=2.4,<4.0)"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -2884,4 +2883,4 @@ bbs = ["ursa-bbs-signatures"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "91116c343135f25794b717d1afb2870d7f230adea0746df19bb9098c1e995d04" +content-hash = "68ee696d9d85f76bc11c3de934af0469b55417504eeac443ca81bdb5bb9cb0a3" diff --git a/pyproject.toml b/pyproject.toml index 8138372dbc..4b78512b0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ ecdsa="~0.19.0" jsonpath-ng="1.6.1" Markdown="~3.6" markupsafe="2.0.1" -marshmallow="~3.20.1" +marshmallow="~3.21.3" nest_asyncio="~1.6.0" packaging="~23.2" portalocker="~2.8.2" From 921a73e2d9b24853248974b6b5b595c73333ba69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:18:53 -0700 Subject: [PATCH 29/31] chore(deps): Bump urllib3 in /demo/playground/examples in the pip group (#3045) Bumps the pip group in /demo/playground/examples with 1 update: [urllib3](https://github.com/urllib3/urllib3). Updates `urllib3` from 2.2.1 to 2.2.2 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- demo/playground/examples/poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/playground/examples/poetry.lock b/demo/playground/examples/poetry.lock index 276a150f88..20b614400c 100644 --- a/demo/playground/examples/poetry.lock +++ b/demo/playground/examples/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "asynctest" @@ -268,13 +268,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] From d5f70e10a9a7cb87030379ada06caa2d47ec8049 Mon Sep 17 00:00:00 2001 From: jamshale <31809382+jamshale@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:30:16 -0700 Subject: [PATCH 30/31] Fix - only run integration tests on opened PR's (#3042) Signed-off-by: jamshale --- .github/workflows/integrationtests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index cefa6fc430..9a9b8fc0b8 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + types: [opened, synchronize, reopened, ready_for_review] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,7 +20,7 @@ defaults: jobs: test: runs-on: ubuntu-latest - if: (github.event_name == 'pull_request' && !github.event.pull_request.draft && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') + if: (github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') outputs: is_release: ${{ steps.check_if_release.outputs.is_release }} steps: From cf2d34b0c7ba9b7232d1eda1388d532d69e6f41d Mon Sep 17 00:00:00 2001 From: jamshale <31809382+jamshale@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:35:29 -0700 Subject: [PATCH 31/31] Fix and refactor anoncreds revocation recovery (#3029) * Fix and refactor anoncreds revocation recovery Signed-off-by: jamshale * Add some unit tests Signed-off-by: jamshale * Refactor / Add more unit tests Signed-off-by: jamshale * Add tests / Refactor Signed-off-by: jamshale * Add a couple more unit tests Signed-off-by: jamshale --------- Signed-off-by: jamshale --- .../anoncreds/default/legacy_indy/recover.py | 126 +++++++ .../anoncreds/default/legacy_indy/registry.py | 253 ++++++++----- .../default/legacy_indy/tests/test_recover.py | 190 ++++++++++ .../legacy_indy/tests/test_registry.py | 332 ++++++++++++++++-- aries_cloudagent/anoncreds/revocation.py | 8 +- .../revocation_anoncreds/routes.py | 5 +- 6 files changed, 787 insertions(+), 127 deletions(-) create mode 100644 aries_cloudagent/anoncreds/default/legacy_indy/recover.py create mode 100644 aries_cloudagent/anoncreds/default/legacy_indy/tests/test_recover.py diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/recover.py b/aries_cloudagent/anoncreds/default/legacy_indy/recover.py new file mode 100644 index 0000000000..1933e69763 --- /dev/null +++ b/aries_cloudagent/anoncreds/default/legacy_indy/recover.py @@ -0,0 +1,126 @@ +"""Recover a revocation registry.""" + +import hashlib +import logging +import time + +import aiohttp +import base58 +import indy_vdr +from anoncreds import ( + RevocationRegistry, + RevocationRegistryDefinition, +) + +from ...models.anoncreds_revocation import RevList + +LOGGER = logging.getLogger(__name__) + + +""" +This module calculates a new ledger accumulator, based on the revocation status +on the ledger vs revocations recorded in the wallet. +The calculated transaction can be written to the ledger to get the ledger back +in sync with the wallet. +This function can be used if there were previous revocation errors (i.e. the +credential revocation was successfully written to the wallet but the ledger write +failed.) +""" + + +class RevocRecoveryException(Exception): + """Raise exception generating the recovery transaction.""" + + +async def _check_tails_hash_for_inconsistency(tails_location: str, tails_hash: str): + async with aiohttp.ClientSession() as session: + LOGGER.debug("Tails URL: %s", tails_location) + tails_data_http_response = await session.get(tails_location) + tails_data = await tails_data_http_response.read() + remote_tails_hash = base58.b58encode( + hashlib.sha256(tails_data).digest() + ).decode("utf-8") + if remote_tails_hash != tails_hash: + raise RevocRecoveryException( + f"Tails hash mismatch {remote_tails_hash} {tails_hash}" + ) + else: + LOGGER.debug(f"Checked tails hash: {tails_hash}") + + +async def fetch_txns(genesis_txns: str, registry_id: str, issuer_id: str) -> tuple[ + dict, + set[int], +]: + """Fetch tails file and revocation registry information.""" + + LOGGER.debug(f"Fetch revocation registry def {registry_id} from ledger") + revoc_reg_delta_request = indy_vdr.ledger.build_get_revoc_reg_def_request( + None, registry_id + ) + + pool = await indy_vdr.open_pool(transactions=genesis_txns) + result = await pool.submit_request(revoc_reg_delta_request) + if not result["data"]: + raise RevocRecoveryException(f"Registry definition not found for {registry_id}") + + # Load the anoncreds revocation registry definition + rev_reg_def_raw = result["data"] + rev_reg_def_raw["ver"] = "1.0" + rev_reg_def_raw["issuerId"] = issuer_id + revoc_reg_def = RevocationRegistryDefinition.load(rev_reg_def_raw) + + await _check_tails_hash_for_inconsistency( + revoc_reg_def.tails_location, revoc_reg_def.tails_hash + ) + + LOGGER.debug(f"Fetch revocation registry delta {registry_id} from ledger") + to_timestamp = int(time.time()) + revoc_reg_delta_request = indy_vdr.ledger.build_get_revoc_reg_delta_request( + None, registry_id, None, to_timestamp + ) + result = await pool.submit_request(revoc_reg_delta_request) + if not result["data"]: + raise RevocRecoveryException("Error fetching delta from ledger") + + registry_from_ledger = result["data"]["value"]["accum_to"] + registry_from_ledger["ver"] = "1.0" + revoked = set(result["data"]["value"]["revoked"]) + LOGGER.debug("Ledger revoked indexes: %s", revoked) + + return registry_from_ledger, revoked + + +async def generate_ledger_rrrecovery_txn(genesis_txns: str, rev_list: RevList): + """Generate a new ledger accum entry, using the wallet value if revocations ahead of ledger.""" # noqa: E501 + + registry_from_ledger, prev_revoked = await fetch_txns( + genesis_txns, rev_list.rev_reg_def_id, rev_list.issuer_id + ) + + set_revoked = { + index for index, value in enumerate(rev_list.revocation_list) if value == 1 + } + mismatch = prev_revoked - set_revoked + if mismatch: + LOGGER.warning( + "Credential index(es) revoked on the ledger, but not in wallet: %s", + mismatch, + ) + + updates = set_revoked - prev_revoked + if not updates: + LOGGER.debug("No updates to perform") + return {} + else: + LOGGER.debug("New revoked indexes: %s", updates) + + # Prepare the transaction to write to the ledger + registry = RevocationRegistry.load(registry_from_ledger) + registry = registry.to_dict() + registry["ver"] = "1.0" + registry["value"]["prevAccum"] = registry_from_ledger["value"]["accum"] + registry["value"]["accum"] = rev_list.current_accumulator + registry["value"]["issued"] = [] + registry["value"]["revoked"] = list(updates) + return registry diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 582a7ec0c6..21f199454e 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -1,11 +1,16 @@ """Legacy Indy Registry.""" -import json +import asyncio import logging import re from asyncio import shield from typing import List, Optional, Pattern, Sequence, Tuple +from anoncreds import ( + CredentialDefinition, + RevocationRegistryDefinition, + RevocationRegistryDefinitionPrivate, +) from base58 import alphabet from uuid_utils import uuid4 @@ -13,14 +18,18 @@ from ....cache.base import BaseCache from ....config.injection_context import InjectionContext from ....core.event_bus import EventBus -from ....core.profile import Profile +from ....core.profile import Profile, ProfileSession from ....ledger.base import BaseLedger from ....ledger.error import ( LedgerError, LedgerObjectAlreadyExistsError, LedgerTransactionError, ) -from ....ledger.merkel_validation.constants import GET_SCHEMA +from ....ledger.merkel_validation.constants import ( + GET_REVOC_REG_DELTA, + GET_REVOC_REG_ENTRY, + GET_SCHEMA, +) from ....ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, IndyLedgerRequestsExecutor, @@ -33,7 +42,6 @@ ) from ....protocols.endorse_transaction.v1_0.util import is_author_role from ....revocation_anoncreds.models.issuer_cred_rev_record import IssuerCredRevRecord -from ....revocation_anoncreds.recover import generate_ledger_rrrecovery_txn from ....storage.error import StorageError from ....utils import sentinel from ....wallet.did_info import DIDInfo @@ -47,7 +55,7 @@ BaseAnonCredsResolver, ) from ...events import RevListFinishedEvent -from ...issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...issuer import CATEGORY_CRED_DEF, AnonCredsIssuer, AnonCredsIssuerError from ...models.anoncreds_cred_def import ( CredDef, CredDefResult, @@ -72,12 +80,29 @@ SchemaResult, SchemaState, ) +from ...revocation import ( + CATEGORY_REV_LIST, + CATEGORY_REV_REG_DEF, + CATEGORY_REV_REG_DEF_PRIVATE, +) +from .recover import generate_ledger_rrrecovery_txn LOGGER = logging.getLogger(__name__) +# Defaults DEFAULT_CRED_DEF_TAG = "default" DEFAULT_SIGNATURE_TYPE = "CL" +# Common error messages +NO_LEDGER_AVAILABLE_MSG = "No ledger available" +MISSING_WALLET_TYPE_MSG = ": missing wallet-type?" +TRANSACTION_MANAGER_FAILED_MSG = "Transaction manager failed to create request: " +FAILED_TO_STORE_TRANSACTION_RECORD = "Failed to store transaction record" + +# Common settings +ENDORSER_AUTO = "endorser.auto_request" +WALLET_TYPE = "wallet.type" + class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry.""" @@ -163,9 +188,9 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: ) if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" + reason = NO_LEDGER_AVAILABLE_MSG + if not profile.settings.get_value(WALLET_TYPE): + reason += MISSING_WALLET_TYPE_MSG raise AnonCredsResolutionError(reason) async with ledger: @@ -207,7 +232,7 @@ async def register_schema( # Assume endorser role on the network, no option for 3rd-party endorser ledger = profile.inject_or(BaseLedger) if not ledger: - raise AnonCredsRegistrationError("No ledger available") + raise AnonCredsRegistrationError(NO_LEDGER_AVAILABLE_MSG) # Translate schema into format expected by Indy LOGGER.debug("Registering schema: %s", schema_id) @@ -276,9 +301,9 @@ async def register_schema( meta_data=meta_data, ) except StorageError: - raise AnonCredsRegistrationError("Failed to store transaction record") + raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD) - if profile.settings.get("endorser.auto_request"): + if profile.settings.get(ENDORSER_AUTO): try: ( transaction, @@ -286,7 +311,7 @@ async def register_schema( ) = await transaction_manager.create_request(transaction=transaction) except (StorageError, TransactionManagerError) as err: raise AnonCredsRegistrationError( - "Transaction manager failed to create request: " + err.roll_up + TRANSACTION_MANAGER_FAILED_MSG + err.roll_up ) from err responder = profile.inject(BaseResponder) @@ -324,9 +349,9 @@ async def get_credential_definition( txn_record_type=GET_CRED_DEF, ) if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" + reason = NO_LEDGER_AVAILABLE_MSG + if not profile.settings.get_value(WALLET_TYPE): + reason += MISSING_WALLET_TYPE_MSG raise AnonCredsResolutionError(reason) async with ledger: @@ -367,7 +392,7 @@ async def register_credential_definition( ledger = profile.inject_or(BaseLedger) if not ledger: - raise AnonCredsRegistrationError("No ledger available") + raise AnonCredsRegistrationError(NO_LEDGER_AVAILABLE_MSG) # Check if in wallet but not on ledger issuer = AnonCredsIssuer(profile) @@ -463,9 +488,9 @@ async def register_credential_definition( meta_data=meta_data, ) except StorageError: - raise AnonCredsRegistrationError("Failed to store transaction record") + raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD) - if profile.settings.get("endorser.auto_request"): + if profile.settings.get(ENDORSER_AUTO): try: ( transaction, @@ -473,7 +498,7 @@ async def register_credential_definition( ) = await transaction_manager.create_request(transaction=transaction) except (StorageError, TransactionManagerError) as err: raise AnonCredsRegistrationError( - "Transaction manager failed to create request: " + err.roll_up + TRANSACTION_MANAGER_FAILED_MSG + err.roll_up ) from err responder = profile.inject(BaseResponder) @@ -511,9 +536,9 @@ async def get_revocation_registry_definition( txn_record_type=GET_CRED_DEF, ) if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" + reason = NO_LEDGER_AVAILABLE_MSG + if not profile.settings.get_value(WALLET_TYPE): + reason += MISSING_WALLET_TYPE_MSG raise AnonCredsResolutionError(reason) async with ledger: @@ -555,7 +580,7 @@ async def register_revocation_registry_definition( ledger = profile.inject(BaseLedger) if not ledger: - raise AnonCredsRegistrationError("No ledger available") + raise AnonCredsRegistrationError(NO_LEDGER_AVAILABLE_MSG) # Translate anoncreds object to indy object indy_rev_reg_def = { @@ -631,9 +656,9 @@ async def register_revocation_registry_definition( meta_data=meta_data, ) except StorageError: - raise AnonCredsRegistrationError("Failed to store transaction record") + raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD) - if profile.settings.get("endorser.auto_request"): + if profile.settings.get(ENDORSER_AUTO): try: ( transaction, @@ -641,7 +666,7 @@ async def register_revocation_registry_definition( ) = await transaction_manager.create_request(transaction=transaction) except (StorageError, TransactionManagerError) as err: raise AnonCredsRegistrationError( - "Transaction manager failed to create request: " + err.roll_up + TRANSACTION_MANAGER_FAILED_MSG + err.roll_up ) from err responder = profile.inject(BaseResponder) @@ -705,9 +730,9 @@ async def _get_ledger(self, profile: Profile, rev_reg_def_id: str): txn_record_type=GET_CRED_DEF, ) if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" + reason = NO_LEDGER_AVAILABLE_MSG + if not profile.settings.get_value(WALLET_TYPE): + reason += MISSING_WALLET_TYPE_MSG raise AnonCredsResolutionError(reason) return ledger_id, ledger @@ -776,8 +801,15 @@ async def _revoc_reg_entry_with_fix( endorser_did: str = None, ) -> dict: """Send a revocation registry entry to the ledger with fixes if needed.""" - # TODO Handle multitenancy and multi-ledger (like in get cred def) - ledger = profile.inject(BaseLedger) + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) + _, ledger = await ledger_exec_inst.get_ledger_for_identifier( + rev_list.rev_reg_def_id, + txn_record_type=GET_REVOC_REG_ENTRY, + ) try: async with ledger: @@ -817,7 +849,6 @@ async def _revoc_reg_entry_with_fix( "Ledger update failed due to TAA Issue" ) from err else: - # not sure what happened, raise an error LOGGER.exception("Ledger update failed due to unknown issue") raise AnonCredsRegistrationError( "Ledger update failed due to unknown issue" @@ -892,9 +923,9 @@ async def register_revocation_list( meta_data=meta_data, ) except StorageError: - raise AnonCredsRegistrationError("Failed to store transaction record") + raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD) - if profile.settings.get("endorser.auto_request"): + if profile.settings.get(ENDORSER_AUTO): try: ( transaction, @@ -902,7 +933,7 @@ async def register_revocation_list( ) = await transaction_manager.create_request(transaction=transaction) except (StorageError, TransactionManagerError) as err: raise AnonCredsRegistrationError( - "Transaction manager failed to create request: " + err.roll_up + TRANSACTION_MANAGER_FAILED_MSG + err.roll_up ) from err responder = profile.inject(BaseResponder) @@ -1006,9 +1037,9 @@ async def update_revocation_list( meta_data=meta_data, ) except StorageError: - raise AnonCredsRegistrationError("Failed to store transaction record") + raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD) - if profile.settings.get("endorser.auto_request"): + if profile.settings.get(ENDORSER_AUTO): try: ( transaction, @@ -1016,7 +1047,7 @@ async def update_revocation_list( ) = await transaction_manager.create_request(transaction=transaction) except (StorageError, TransactionManagerError) as err: raise AnonCredsRegistrationError( - "Transaction manager failed to create request: " + err.roll_up + TRANSACTION_MANAGER_FAILED_MSG + err.roll_up ) from err responder = profile.inject(BaseResponder) @@ -1047,64 +1078,56 @@ async def fix_ledger_entry( endorser_did: str = None, ) -> Tuple[dict, dict, dict]: """Fix the ledger entry to match wallet-recorded credentials.""" - # get rev reg delta (revocations published to ledger) - ledger = profile.inject(BaseLedger) + + def _wallet_accumalator_matches_ledger_list( + rev_list: RevList, rev_reg_delta: dict + ) -> bool: + return ( + rev_reg_delta.get("value") + and rev_list.current_accumulator == rev_reg_delta["value"]["accum"] + ) + + applied_txn = {} + recovery_txn = {} + + LOGGER.debug("Fixing ledger entry for revocation list...") + + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) + _, ledger = await ledger_exec_inst.get_ledger_for_identifier( + rev_list.rev_reg_def_id, + txn_record_type=GET_REVOC_REG_DELTA, + ) + async with ledger: (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( rev_list.rev_reg_def_id ) - # get rev reg records from wallet (revocations and list) - recs = [] - rec_count = 0 - accum_count = 0 - recovery_txn = {} - applied_txn = {} async with profile.session() as session: - recs = await IssuerCredRevRecord.query_by_ids( - session, rev_reg_id=rev_list.rev_reg_def_id - ) - revoked_ids = [] - for rec in recs: - if rec.state == IssuerCredRevRecord.STATE_REVOKED: - revoked_ids.append(int(rec.cred_rev_id)) - if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]: - # await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED) - rec_count += 1 + LOGGER.debug(f"revocation_list = {rev_list.revocation_list}") + LOGGER.debug(f"rev_reg_delta = {rev_reg_delta.get('value')}") - LOGGER.debug(">>> fixed entry recs count = %s", rec_count) - LOGGER.debug( - ">>> rev_list.revocation_list: %s", - rev_list.revocation_list, - ) - LOGGER.debug( - '>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value") + rev_list = await self._sync_wallet_rev_list_with_issuer_cred_rev_records( + session, rev_list ) - # if we had any revocation discrepancies, check the accumulator value - if rec_count > 0: - if (rev_list.current_accumulator and rev_reg_delta.get("value")) and ( - rev_list.current_accumulator != rev_reg_delta["value"]["accum"] - ): - # self.revoc_reg_entry = rev_reg_delta["value"] - # await self.save(session) - accum_count += 1 - - calculated_txn = await generate_ledger_rrrecovery_txn( - genesis_transactions, - rev_list.rev_reg_def_id, - revoked_ids, + if not _wallet_accumalator_matches_ledger_list(rev_list, rev_reg_delta): + + recovery_txn = await generate_ledger_rrrecovery_txn( + genesis_transactions, rev_list ) - recovery_txn = json.loads(calculated_txn.to_json()) - LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update) - if apply_ledger_update: + if apply_ledger_update and recovery_txn: ledger = session.inject_or(BaseLedger) if not ledger: - reason = "No ledger available" - if not session.context.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" + reason = NO_LEDGER_AVAILABLE_MSG + if not session.context.settings.get_value(WALLET_TYPE): + reason += MISSING_WALLET_TYPE_MSG raise LedgerError(reason=reason) async with ledger: @@ -1119,6 +1142,72 @@ async def fix_ledger_entry( return (rev_reg_delta, recovery_txn, applied_txn) + async def _sync_wallet_rev_list_with_issuer_cred_rev_records( + self, session: ProfileSession, rev_list: RevList + ) -> RevList: + """Sync the wallet revocation list with the issuer cred rev records.""" + + async def _revoked_issuer_cred_rev_record_ids() -> List[int]: + cred_rev_records = await IssuerCredRevRecord.query_by_ids( + session, rev_reg_id=rev_list.rev_reg_def_id + ) + return [ + int(rec.cred_rev_id) + for rec in cred_rev_records + if rec.state == "revoked" + ] + + def _revocation_list_to_array_of_indexes( + revocation_list: List[int], + ) -> List[int]: + return [index for index, value in enumerate(revocation_list) if value == 1] + + revoked = await _revoked_issuer_cred_rev_record_ids() + if revoked == _revocation_list_to_array_of_indexes(rev_list.revocation_list): + return rev_list + + # The revocation list is out of sync with the issuer cred rev records + # Recreate the revocation list with the issuer cred rev records + revoc_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, rev_list.rev_reg_def_id + ) + cred_def_entry = await session.handle.fetch( + CATEGORY_CRED_DEF, + RevRegDef.deserialize(revoc_reg_def_entry.value_json).cred_def_id, + ) + revoc_reg_def_private_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, rev_list.rev_reg_def_id + ) + updated_list = await asyncio.get_event_loop().run_in_executor( + None, + lambda: rev_list.to_native().update( + cred_def=CredentialDefinition.load(cred_def_entry.value_json), + rev_reg_def=RevocationRegistryDefinition.load( + revoc_reg_def_entry.value_json + ), + rev_reg_def_private=RevocationRegistryDefinitionPrivate.load( + revoc_reg_def_private_entry.raw_value + ), + issued=None, + revoked=revoked, + timestamp=None, + ), + ) + rev_list_entry_update = await session.handle.fetch( + CATEGORY_REV_LIST, rev_list.rev_reg_def_id, for_update=True + ) + tags = rev_list_entry_update.tags + rev_list_entry_update = rev_list_entry_update.value_json + rev_list_entry_update["rev_list"] = updated_list.to_dict() + + await session.handle.replace( + CATEGORY_REV_LIST, + rev_list.rev_reg_def_id, + value_json=rev_list_entry_update, + tags=tags, + ) + return RevList.deserialize(updated_list.to_json()) + async def txn_submit( self, ledger: BaseLedger, diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_recover.py b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_recover.py new file mode 100644 index 0000000000..2973c1824b --- /dev/null +++ b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_recover.py @@ -0,0 +1,190 @@ +"""Test Recover.""" + +import hashlib +from unittest import IsolatedAsyncioTestCase + +import aiohttp +import base58 +import indy_vdr +import pytest +from anoncreds import RevocationRegistryDefinition + +from aries_cloudagent.tests import mock + +from ....models.anoncreds_revocation import RevList, RevRegDef, RevRegDefValue +from ..recover import ( + RevocRecoveryException, + _check_tails_hash_for_inconsistency, + fetch_txns, + generate_ledger_rrrecovery_txn, +) + +GENESIS = '{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"172.17.0.2","client_port":9702,"node_ip":"172.17.0.2","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"}\n{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node2","blskey":"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk","blskey_pop":"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5","client_ip":"172.17.0.2","client_port":9704,"node_ip":"172.17.0.2","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"},"metadata":{"from":"EbP4aYNeTHL6q385GuVpRV"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"},"ver":"1"}\n{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"172.17.0.2","client_port":9706,"node_ip":"172.17.0.2","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"}\n{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"172.17.0.2","client_port":9708,"node_ip":"172.17.0.2","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}' + + +rev_reg_def = RevRegDef( + tag="tag", + cred_def_id="CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + value=RevRegDefValue( + max_cred_num=100, + public_keys={ + "accum_key": {"z": "1 0BB...386"}, + }, + tails_hash="58NNWYnVxVFzAfUztwGSNBL4551XNq6nXk56pCiKJxxt", + tails_location="http://tails-server.com", + ), + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + type="CL_ACCUM", +) + + +@pytest.mark.anoncreds +class TestLegacyIndyRecover(IsolatedAsyncioTestCase): + + @mock.patch.object( + indy_vdr, + "open_pool", + mock.CoroutineMock( + return_value=mock.MagicMock( + submit_request=mock.CoroutineMock(return_value={"data": {}}) + ) + ), + ) + async def test_fetch_txns_empty_data_from_ledger(self, *_): + with self.assertRaises(RevocRecoveryException): + await fetch_txns( + GENESIS, + "4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + "CsQY9MGeD3CQP4EyuVFo5m", + ) + + @mock.patch.object( + RevocationRegistryDefinition, + "load", + return_value=rev_reg_def.value, + ) + @mock.patch.object( + indy_vdr, + "open_pool", + mock.CoroutineMock( + return_value=mock.MagicMock( + submit_request=mock.CoroutineMock( + return_value={ + "data": { + "ver": "1.0", + "value": { + "accum_to": {}, + "revoked": [1, 0, 1, 0], + }, + } + } + ) + ) + ), + ) + @mock.patch( + "aries_cloudagent.anoncreds.default.legacy_indy.recover._check_tails_hash_for_inconsistency" + ) + async def test_fetch_txns(self, *_): + result = await fetch_txns( + GENESIS, + "4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + "CsQY9MGeD3CQP4EyuVFo5m", + ) + assert isinstance(result, tuple) + + @mock.patch( + "aries_cloudagent.anoncreds.default.legacy_indy.recover.fetch_txns", + mock.CoroutineMock( + return_value=( + { + "ver": "1.0", + "value": { + "accum": "2 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C" + }, + }, + {0, 1, 2}, + ) + ), + ) + async def test_generate_ledger_rrrecovery_txn(self): + + # Has updates + result = await generate_ledger_rrrecovery_txn( + GENESIS, + RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[1, 1, 1, 1], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ), + ) + assert result != {} + # Doesn't have updates + result = await generate_ledger_rrrecovery_txn( + GENESIS, + RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[1, 1, 1, 0], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ), + ) + assert result == {} + + # Logs waring when ledger has revoked indexes not in wallet + with mock.patch( + "aries_cloudagent.anoncreds.default.legacy_indy.recover.LOGGER" + ) as mock_logger: + result = await generate_ledger_rrrecovery_txn( + GENESIS, + RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[1, 0, 0, 0], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ), + ) + assert mock_logger.warning.called + assert result == {} + + @mock.patch.object( + aiohttp, + "ClientSession", + mock.MagicMock( + return_value=mock.MagicMock( + get=mock.CoroutineMock( + return_value=mock.MagicMock( + read=mock.CoroutineMock(return_value=b"some data") + ) + ) + ) + ), + ) + @mock.patch.object( + base58, + "b58encode", + side_effect=[ + b"58NNWYnVxVFzAfUztwGSNBL4551XNq6nXk56pCiKJxxt", + b"58NNWYnVxVFzAfUztwGSNBL4551XNq6nXk56pCiKJxxz", + ], + ) + @mock.patch.object( + hashlib, + "sha256", + return_value=mock.MagicMock(digest=mock.MagicMock()), + ) + async def test_check_tails_hash_for_inconsistency(self, *_): + # Matches + await _check_tails_hash_for_inconsistency( + "http://tails-server.com", "58NNWYnVxVFzAfUztwGSNBL4551XNq6nXk56pCiKJxxt" + ) + # Mismatch + with self.assertRaises(RevocRecoveryException): + await _check_tails_hash_for_inconsistency( + "http://tails-server.com", + "58NNWYnVxVFzAfUztwGSNBL4551XNq6nXk56pCiKJxxt", + ) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py index 830a0bb722..4e761fca2d 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py @@ -5,7 +5,12 @@ from unittest import IsolatedAsyncioTestCase import pytest -from anoncreds import Schema +from anoncreds import ( + CredentialDefinition, + RevocationRegistryDefinition, + RevocationRegistryDefinitionPrivate, + Schema, +) from base58 import alphabet from .....anoncreds.base import ( @@ -18,9 +23,13 @@ ) from .....askar.profile_anon import AskarAnoncredsProfile from .....connections.models.conn_record import ConnRecord -from .....core.in_memory.profile import InMemoryProfile +from .....core.event_bus import EventBus +from .....core.in_memory.profile import InMemoryProfile, InMemoryProfileSession from .....ledger.base import BaseLedger from .....ledger.error import LedgerObjectAlreadyExistsError +from .....ledger.multiple_ledger.ledger_requests_executor import ( + IndyLedgerRequestsExecutor, +) from .....messaging.responder import BaseResponder from .....protocols.endorse_transaction.v1_0.manager import ( TransactionManager, @@ -28,6 +37,7 @@ from .....protocols.endorse_transaction.v1_0.models.transaction_record import ( TransactionRecord, ) +from .....revocation_anoncreds.models.issuer_cred_rev_record import IssuerCredRevRecord from .....tests import mock from ....issuer import AnonCredsIssuer from ....models.anoncreds_cred_def import ( @@ -41,6 +51,7 @@ RevListResult, RevRegDef, RevRegDefResult, + RevRegDefState, RevRegDefValue, ) from .. import registry as test_module @@ -81,6 +92,55 @@ ) +class MockTxn: + def to_json(self): + return json.dumps(self.__dict__) + + +class MockRevRegDefEntry: + def __init__(self, name="name"): + self.name = name + + tags = { + "state": RevRegDefState.STATE_ACTION, + } + value = "mock_value" + value_json = { + "value": { + "maxCredNum": 100, + "publicKeys": {"accumKey": {"z": "1 0BB...386"}}, + "tailsHash": "string", + "tailsLocation": "string", + }, + "credDefId": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + "issuerId": "CsQY9MGeD3CQP4EyuVFo5m", + "revocDefType": "CL_ACCUM", + "tag": "string", + } + + +class MockCredDefEntry: + value_json = {} + + +class MockRevListEntry: + tags = {} + value = "mock_value" + value_json = { + "issuerId": "CsQY9MGeD3CQP4EyuVFo5m", + "revRegDefId": "4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + "revocationList": [0, 1, 0, 0], + "currentAccumulator": "21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + "timestamp": 1669640864487, + } + + def to_json(self): + return self.value_json + + def to_dict(self): + return self.value_json + + @pytest.mark.anoncreds class TestLegacyIndyRegistry(IsolatedAsyncioTestCase): async def asyncSetUp(self): @@ -738,11 +798,29 @@ async def test_txn_submit(self): result = await self.registry.txn_submit(ledger, "test_txn") assert result == "transaction_id" - async def test_register_revocation_list_no_endorsement(self): - self.profile.context.injector.bind_instance( - BaseLedger, - mock.MagicMock(send_revoc_reg_entry=mock.CoroutineMock(return_value=1)), + @mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + return_value=( + "id", + mock.MagicMock( + send_revoc_reg_entry=mock.CoroutineMock(return_value="transaction_id") + ), + ), + ) + @mock.patch.object(InMemoryProfileSession, "handle") + async def test_register_revocation_list_no_endorsement( + self, mock_handle, mock_send_revoc_reg_entry + ): + self.profile.inject_or = mock.MagicMock() + mock_handle.fetch = mock.CoroutineMock( + side_effect=[ + mock.CoroutineMock(return_value=None), + mock.CoroutineMock(return_value=None), + mock.CoroutineMock(return_value=None), + ] ) + result = await self.registry.register_revocation_list( self.profile, RevRegDef( @@ -770,9 +848,7 @@ async def test_register_revocation_list_no_endorsement(self): ) assert isinstance(result, RevListResult) - assert self.profile.context.injector.get_provider( - BaseLedger - )._instance.send_revoc_reg_entry.called + assert mock_send_revoc_reg_entry.called @mock.patch.object( ConnRecord, @@ -786,17 +862,28 @@ async def test_register_revocation_list_no_endorsement(self): "create_record", return_value=TransactionRecord(), ) - async def test_register_revocation_list_with_author_role( - self, mock_create_record, mock_endorsement_conn - ): - self.profile.context.injector.bind_instance( - BaseLedger, + @mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + return_value=( + "id", mock.MagicMock( send_revoc_reg_entry=mock.CoroutineMock( - return_value=("id", {"signed_txn": "txn"}) + return_value=( + "rev_reg_def_id", + { + "signed_txn": "txn", + }, + ) ) ), - ) + ), + ) + async def test_register_revocation_list_with_author_role( + self, mock_send_revoc_reg_entry, mock_create_record, _ + ): + + self.profile.inject_or = mock.MagicMock() self.profile.settings.set_value("endorser.author", True) result = await self.registry.register_revocation_list( @@ -828,10 +915,8 @@ async def test_register_revocation_list_with_author_role( ) assert isinstance(result, RevListResult) - assert self.profile.context.injector.get_provider( - BaseLedger - )._instance.send_revoc_reg_entry.called assert mock_create_record.called + assert mock_send_revoc_reg_entry.called @mock.patch.object( ConnRecord, @@ -845,17 +930,27 @@ async def test_register_revocation_list_with_author_role( "create_record", return_value=TransactionRecord(), ) - async def test_register_revocation_list_with_create_transaction_option( - self, mock_create_record, mock_endorsement_conn - ): - self.profile.context.injector.bind_instance( - BaseLedger, + @mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + return_value=( + "id", mock.MagicMock( send_revoc_reg_entry=mock.CoroutineMock( - return_value=("id", {"signed_txn": "txn"}) + return_value=( + "rev_reg_def_id", + { + "signed_txn": "txn", + }, + ) ) ), - ) + ), + ) + async def test_register_revocation_list_with_create_transaction_option( + self, mock_send_revoc_reg_entry, mock_create_record, _ + ): + self.profile.inject_or = mock.MagicMock() result = await self.registry.register_revocation_list( self.profile, @@ -887,10 +982,8 @@ async def test_register_revocation_list_with_create_transaction_option( ) assert isinstance(result, RevListResult) - assert self.profile.context.injector.get_provider( - BaseLedger - )._instance.send_revoc_reg_entry.called assert mock_create_record.called + assert mock_send_revoc_reg_entry.called @mock.patch.object( ConnRecord, @@ -909,17 +1002,27 @@ async def test_register_revocation_list_with_create_transaction_option( "create_request", return_value=(TransactionRecord(), "transaction_request"), ) - async def test_register_revocation_list_with_create_transaction_option_and_auto_request( - self, mock_create_request, mock_create_record, mock_endorsement_conn - ): - self.profile.context.injector.bind_instance( - BaseLedger, + @mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + return_value=( + "id", mock.MagicMock( send_revoc_reg_entry=mock.CoroutineMock( - return_value=("id", {"signed_txn": "txn"}) + return_value=( + "rev_reg_def_id", + { + "signed_txn": "txn", + }, + ) ) ), - ) + ), + ) + async def test_register_revocation_list_with_create_transaction_option_and_auto_request( + self, mock_send_revoc_reg_entry, mock_create_request, mock_create_record, _ + ): + self.profile.inject_or = mock.MagicMock() self.profile.context.injector.bind_instance( BaseResponder, mock.MagicMock(send=mock.CoroutineMock(return_value=None)), @@ -956,11 +1059,162 @@ async def test_register_revocation_list_with_create_transaction_option_and_auto_ ) assert isinstance(result, RevListResult) - assert self.profile.context.injector.get_provider( - BaseLedger - )._instance.send_revoc_reg_entry.called assert mock_create_record.called assert mock_create_request.called + assert mock_send_revoc_reg_entry.called assert self.profile.context.injector.get_provider( BaseResponder )._instance.send.called + + @mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + return_value=( + "id", + mock.MagicMock( + get_revoc_reg_delta=mock.CoroutineMock( + return_value=( + { + "value": { + "accum": "21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + } + }, + 123, + ) + ) + ), + ), + ) + @mock.patch.object( + test_module.LegacyIndyRegistry, + "_sync_wallet_rev_list_with_issuer_cred_rev_records", + mock.CoroutineMock( + return_value=RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="2 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[1, 0, 1, 0], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ) + ), + ) + @mock.patch( + "aries_cloudagent.anoncreds.default.legacy_indy.registry.generate_ledger_rrrecovery_txn", + mock.CoroutineMock(return_value=MockTxn()), + ) + async def test_fix_ledger_entry(self, *_): + + self.profile.context.injector.bind_instance( + BaseLedger, + mock.MagicMock(send_revoc_reg_entry=mock.CoroutineMock(return_value={})), + ) + + self.profile.context.injector.bind_instance( + EventBus, + {}, + ) + + async with self.profile.transaction() as txn: + issuer_cr_rec = IssuerCredRevRecord( + state=IssuerCredRevRecord.STATE_ISSUED, + cred_ex_id="cred_ex_id", + cred_ex_version=IssuerCredRevRecord.VERSION_1, + rev_reg_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + cred_rev_id="cred_rev_id", + ) + await issuer_cr_rec.save( + txn, + reason=("Testing"), + ) + + self.profile.inject_or = mock.MagicMock() + result = await self.registry.fix_ledger_entry( + self.profile, + RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[0, 1, 1, 0], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ), + True, + '{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"172.17.0.2","client_port":9702,"node_ip":"172.17.0.2","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"}\n{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node2","blskey":"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk","blskey_pop":"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5","client_ip":"172.17.0.2","client_port":9704,"node_ip":"172.17.0.2","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"},"metadata":{"from":"EbP4aYNeTHL6q385GuVpRV"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"},"ver":"1"}\n{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"172.17.0.2","client_port":9706,"node_ip":"172.17.0.2","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"}\n{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"172.17.0.2","client_port":9708,"node_ip":"172.17.0.2","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}', + True, + "endorser_did", + ) + + assert isinstance(result, tuple) + + @mock.patch.object(CredentialDefinition, "load") + @mock.patch.object(RevocationRegistryDefinition, "load") + @mock.patch.object(RevocationRegistryDefinitionPrivate, "load") + @mock.patch.object( + IssuerCredRevRecord, + "query_by_ids", + return_value=[ + IssuerCredRevRecord( + state=IssuerCredRevRecord.STATE_REVOKED, + cred_ex_id="cred_ex_id", + rev_reg_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + cred_rev_id="1", + ), + IssuerCredRevRecord( + state=IssuerCredRevRecord.STATE_REVOKED, + cred_ex_id="cred_ex_id", + rev_reg_id="4xE68b6S5VRFrKMMG1U95M:5:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + cred_rev_id="2", + ), + ], + ) + @mock.patch.object( + RevList, + "to_native", + return_value=mock.MagicMock( + update=mock.MagicMock(return_value=MockRevListEntry()) + ), + ) + @mock.patch.object(InMemoryProfileSession, "handle") + async def test_sync_wallet_rev_list_with_issuer_cred_rev_records( + self, mock_handle, *_ + ): + async with self.profile.session() as session: + # Matching revocations and rev_list + mock_handle.fetch = mock.CoroutineMock( + side_effect=[ + MockRevRegDefEntry(), + MockCredDefEntry(), + mock.CoroutineMock(return_value=None), + ] + ) + result = await self.registry._sync_wallet_rev_list_with_issuer_cred_rev_records( + session, + RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[0, 1, 1, 0], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ), + ) + assert isinstance(result, RevList) + # Non-matching revocations and rev_list + mock_handle.fetch = mock.CoroutineMock( + side_effect=[ + MockRevRegDefEntry(), + MockCredDefEntry(), + mock.CoroutineMock(return_value=None), + MockRevListEntry(), + ] + ) + mock_handle.replace = mock.CoroutineMock(return_value=None) + result = await self.registry._sync_wallet_rev_list_with_issuer_cred_rev_records( + session, + RevList( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + current_accumulator="21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C", + revocation_list=[0, 1, 0, 0], + timestamp=1669640864487, + rev_reg_def_id="4xE68b6S5VRFrKMMG1U95M:4:4xE68b6S5VRFrKMMG1U95M:3:CL:59232:default:CL_ACCUM:4ae1cc6c-f6bd-486c-8057-88f2ce74e960", + ), + ) + assert isinstance(result, RevList) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 84a03ef866..a5d49e0dbb 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -577,7 +577,7 @@ async def update_revocation_list( self.profile, rev_reg_def, prev, curr, revoked, options ) - # # TODO Handle `failed` state + # TODO Handle `failed` state try: async with self.profile.session() as session: rev_list_entry_upd = await session.handle.fetch( @@ -1284,7 +1284,7 @@ async def revoke_pending_credentials( for rev_id in cred_revoc_ids: if rev_id < 1 or rev_id > max_cred_num: LOGGER.error( - "Skipping requested credential revocation" + "Skipping requested credential revocation " "on rev reg id %s, cred rev id=%s not in range", revoc_reg_id, rev_id, @@ -1292,7 +1292,7 @@ async def revoke_pending_credentials( failed_crids.add(rev_id) elif rev_id >= rev_info["next_index"]: LOGGER.warning( - "Skipping requested credential revocation" + "Skipping requested credential revocation " "on rev reg id %s, cred rev id=%s not yet issued", revoc_reg_id, rev_id, @@ -1300,7 +1300,7 @@ async def revoke_pending_credentials( failed_crids.add(rev_id) elif rev_list.revocation_list[rev_id] == 1: LOGGER.warning( - "Skipping requested credential revocation" + "Skipping requested credential revocation " "on rev reg id %s, cred rev id=%s already revoked", revoc_reg_id, rev_id, diff --git a/aries_cloudagent/revocation_anoncreds/routes.py b/aries_cloudagent/revocation_anoncreds/routes.py index e6cf3ec7e7..327627db96 100644 --- a/aries_cloudagent/revocation_anoncreds/routes.py +++ b/aries_cloudagent/revocation_anoncreds/routes.py @@ -883,6 +883,7 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest): apply_ledger_update = json.loads(request.query.get("apply_ledger_update", "false")) genesis_transactions = None + recovery_txn = {} try: revocation = AnonCredsRevocation(profile) rev_reg_def = await revocation.get_created_revocation_registry_definition( @@ -944,8 +945,8 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest): return web.json_response( { "rev_reg_delta": rev_reg_delta, - "accum_calculated": recovery_txn, - "accum_fixed": applied_txn, + "recovery_txn": recovery_txn, + "applied_txn": applied_txn, } )