From 2c8fa892b0a054a1aeb98b262063af98d953a6cd Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Wed, 28 Jan 2026 10:02:40 -0500 Subject: [PATCH 1/2] update lts Signed-off-by: Patrick St-Louis --- acapy_agent/anoncreds/events.py | 73 +++++++++++++++++-- acapy_agent/anoncreds/issuer.py | 25 ++++++- acapy_agent/anoncreds/tests/test_issuer.py | 43 +++++++++-- .../anoncreds/tests/test_revocation_setup.py | 2 + poetry.lock | 26 ++++--- pyproject.toml | 3 +- 6 files changed, 144 insertions(+), 28 deletions(-) diff --git a/acapy_agent/anoncreds/events.py b/acapy_agent/anoncreds/events.py index 719c1d5310..f70fbd5634 100644 --- a/acapy_agent/anoncreds/events.py +++ b/acapy_agent/anoncreds/events.py @@ -6,15 +6,73 @@ from ..core.event_bus import Event from .models.revocation import RevRegDef +SCHEMA_FINISHED_EVENT = "anoncreds::schema::finished" CRED_DEF_FINISHED_EVENT = "anoncreds::credential-definition::finished" REV_REG_DEF_FINISHED_EVENT = "anoncreds::revocation-registry-definition::finished" REV_LIST_FINISHED_EVENT = "anoncreds::revocation-list::finished" +SCHEMA_FINISHED_PATTERN = re.compile(SCHEMA_FINISHED_EVENT) CRED_DEF_FINISHED_PATTERN = re.compile(CRED_DEF_FINISHED_EVENT) REV_REG_DEF_FINISHED_PATTERN = re.compile(REV_REG_DEF_FINISHED_EVENT) REV_LIST_FINISHED_PATTERN = re.compile(REV_LIST_FINISHED_EVENT) +class SchemaFinishedPayload(NamedTuple): + """Payload of schema finished event.""" + + schema_id: str + issuer_id: str + name: str + version: str + attr_names: list + options: dict + + +class SchemaFinishedEvent(Event): + """Event for schema finished.""" + + event_topic = SCHEMA_FINISHED_EVENT + + def __init__( + self, + payload: SchemaFinishedPayload, + ): + """Initialize an instance. + + Args: + payload: SchemaFinishedPayload + + """ + self._topic = self.event_topic + self._payload = payload + + @classmethod + def with_payload( + cls, + schema_id: str, + issuer_id: str, + name: str, + version: str, + attr_names: list, + options: Optional[dict] = None, + ): + """With payload.""" + payload = SchemaFinishedPayload( + schema_id=schema_id, + issuer_id=issuer_id, + name=name, + version=version, + attr_names=attr_names, + options=options or {}, + ) + return cls(payload) + + @property + def payload(self) -> SchemaFinishedPayload: + """Return payload.""" + return self._payload + + class CredDefFinishedPayload(NamedTuple): """Payload of cred def finished event.""" @@ -23,6 +81,7 @@ class CredDefFinishedPayload(NamedTuple): issuer_id: str support_revocation: bool max_cred_num: int + tag: str options: dict @@ -49,16 +108,18 @@ def with_payload( issuer_id: str, support_revocation: bool, max_cred_num: int, + tag: str, options: Optional[dict] = None, ): """With payload.""" payload = CredDefFinishedPayload( - schema_id=schema_id, - cred_def_id=cred_def_id, - issuer_id=issuer_id, - support_revocation=support_revocation, - max_cred_num=max_cred_num, - options=options, + schema_id, + cred_def_id, + issuer_id, + support_revocation, + max_cred_num, + tag, + options, ) return cls(payload) diff --git a/acapy_agent/anoncreds/issuer.py b/acapy_agent/anoncreds/issuer.py index f035f9753e..f3925675fb 100644 --- a/acapy_agent/anoncreds/issuer.py +++ b/acapy_agent/anoncreds/issuer.py @@ -24,7 +24,7 @@ from ..protocols.endorse_transaction.v1_0.util import is_author_role from .base import AnonCredsSchemaAlreadyExists, BaseAnonCredsError from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG -from .events import CredDefFinishedEvent +from .events import CredDefFinishedEvent, SchemaFinishedEvent from .models.credential_definition import CredDef, CredDefResult from .models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult, SchemaState from .registry import AnonCredsRegistry @@ -237,9 +237,23 @@ async def create_and_register_schema( async def finish_schema(self, job_id: str, schema_id: str) -> None: """Mark a schema as finished.""" async with self.profile.transaction() as txn: - await self._finish_registration(txn, CATEGORY_SCHEMA, job_id, schema_id) + entry = await self._finish_registration( + txn, CATEGORY_SCHEMA, job_id, schema_id + ) await txn.commit() + schema = AnonCredsSchema.from_json(entry.value) + await self.notify( + SchemaFinishedEvent.with_payload( + schema_id=schema_id, + issuer_id=schema.issuer_id, + name=schema.name, + version=schema.version, + attr_names=schema.attr_names, + options={}, + ) + ) + async def get_created_schemas( self, name: Optional[str] = None, @@ -428,13 +442,17 @@ async def store_credential_definition( ) await txn.commit() if cred_def_result.credential_definition_state.state == STATE_FINISHED: + cred_def = ( + cred_def_result.credential_definition_state.credential_definition + ) await self.notify( CredDefFinishedEvent.with_payload( schema_id=schema_result.schema_id, cred_def_id=identifier, - issuer_id=cred_def_result.credential_definition_state.credential_definition.issuer_id, + issuer_id=cred_def.issuer_id, support_revocation=support_revocation, max_cred_num=max_cred_num, + tag=cred_def.tag, options=options, ) ) @@ -468,6 +486,7 @@ async def finish_cred_def( issuer_id=cred_def.issuer_id, support_revocation=support_revocation, max_cred_num=max_cred_num, + tag=cred_def.tag, options=options, ) ) diff --git a/acapy_agent/anoncreds/tests/test_issuer.py b/acapy_agent/anoncreds/tests/test_issuer.py index 0b9de21ee6..1b0182a85d 100644 --- a/acapy_agent/anoncreds/tests/test_issuer.py +++ b/acapy_agent/anoncreds/tests/test_issuer.py @@ -33,6 +33,7 @@ from ...tests import mock from ...utils.testing import create_test_profile from .. import issuer as test_module +from ..events import SchemaFinishedEvent class MockSchemaEntry: @@ -386,13 +387,43 @@ async def test_create_and_register_schema_with_endorsed_transaction_response_doe assert isinstance(result, SchemaResult) assert mock_store_schema.called - async def test_finish_schema(self): - self.profile.transaction = mock.Mock( - return_value=mock.MagicMock( - commit=mock.CoroutineMock(return_value=None), - ) - ) + @mock.patch.object(test_module.AnonCredsIssuer, "notify") + async def test_finish_schema(self, mock_notify): + # Create a mock entry with a valid schema JSON value + mock_entry = mock.MagicMock() + mock_entry.value = json.dumps({ + "issuerId": "issuer-id", + "name": "test-schema", + "version": "1.0", + "attrNames": ["attr1", "attr2"] + }) + mock_entry.tags = {} + + # Mock the transaction context manager + mock_txn = mock.MagicMock() + mock_handle = mock.MagicMock() + mock_handle.fetch = mock.CoroutineMock(return_value=mock_entry) + mock_handle.insert = mock.CoroutineMock(return_value=None) + mock_handle.remove = mock.CoroutineMock(return_value=None) + mock_txn.handle = mock_handle + mock_txn.commit = mock.CoroutineMock(return_value=None) + mock_txn.__aenter__ = mock.CoroutineMock(return_value=mock_txn) + mock_txn.__aexit__ = mock.CoroutineMock(return_value=None) + + self.profile.transaction = mock.Mock(return_value=mock_txn) + await self.issuer.finish_schema(job_id="job-id", schema_id="schema-id") + + # Verify that SchemaFinishedEvent was emitted + mock_notify.assert_called_once() + call_args = mock_notify.call_args + assert isinstance(call_args[0][0], SchemaFinishedEvent) + event = call_args[0][0] + assert event.payload.schema_id == "schema-id" + assert event.payload.issuer_id == "issuer-id" + assert event.payload.name == "test-schema" + assert event.payload.version == "1.0" + assert event.payload.attr_names == ["attr1", "attr2"] @mock.patch.object(AskarAnonCredsProfileSession, "handle") async def test_get_created_schemas(self, mock_session_handle): diff --git a/acapy_agent/anoncreds/tests/test_revocation_setup.py b/acapy_agent/anoncreds/tests/test_revocation_setup.py index 57f4aab04f..fc5a174e26 100644 --- a/acapy_agent/anoncreds/tests/test_revocation_setup.py +++ b/acapy_agent/anoncreds/tests/test_revocation_setup.py @@ -41,6 +41,7 @@ async def test_on_cred_def_support_revocation_registers_revocation_def( issuer_id="issuer_id", support_revocation=True, max_cred_num=100, + tag="default", options={}, ) ) @@ -63,6 +64,7 @@ async def test_on_cred_def_not_support_rev_option( issuer_id="issuer_id", support_revocation=False, max_cred_num=100, + tag="default", options={}, ) ) diff --git a/poetry.lock b/poetry.lock index d4c839f9a3..a1b39734be 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -183,16 +183,16 @@ files = [ [[package]] name = "anoncreds" -version = "0.2.0" +version = "0.2.3" description = "" optional = false python-versions = ">=3.6.3" groups = ["main"] files = [ - {file = "anoncreds-0.2.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:ec57e224d5f1b8749c3d6ff75bb61229a4f9c31df1ee863835f025c78ec10cd0"}, - {file = "anoncreds-0.2.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:55dd0ad8c8611d2f6af158485dbd2f3c9524694ee4eaf1c5558973f1e436f943"}, - {file = "anoncreds-0.2.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6fb3b28e1f7c283ba27cb5d65ce3dd5303162e17c4311d69cb93402bfd2e3317"}, - {file = "anoncreds-0.2.0-py3-none-win_amd64.whl", hash = "sha256:6c19d86117589ca5cc8f85637d62ebe077c52c34a5de9d1915f5e551458202b1"}, + {file = "anoncreds-0.2.3-py3-none-macosx_10_9_universal2.whl", hash = "sha256:9bc5d6f4404f611e8ad74801fcf1aa05bf4307831edf18bfd9438d811df053fc"}, + {file = "anoncreds-0.2.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:079040da7346fcdd4e70e7103a5644692460c4e88d1d845f6918f9a3e0a6c475"}, + {file = "anoncreds-0.2.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:5fe3172d37a88640a0af65e16a1f6da74f9dbac9d962e77b288dcacbb1c10cfc"}, + {file = "anoncreds-0.2.3-py3-none-win_amd64.whl", hash = "sha256:cd9c747eeff5dc3d975f99671f6e79b1d287c5fb625abf4dafadeaa69bdfc739"}, ] [[package]] @@ -1318,6 +1318,8 @@ python-versions = "*" groups = ["main"] files = [ {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, ] [package.dependencies] @@ -2800,21 +2802,21 @@ files = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "ursa-bbs-signatures" @@ -3049,4 +3051,4 @@ didcommv2 = ["didcomm-messaging"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "4d5c6e1c1b488f57dd93dde8b46655866c5f5651bc365dd75d351dd5550ccca3" +content-hash = "7aef5338ff1226b42352d0c86b3e554baebaaab1e70a8ec6d9458459ba239e7a" diff --git a/pyproject.toml b/pyproject.toml index 3e74f295ee..56ac443b9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ python-json-logger = "^3.2.1" pyyaml = "~6.0.2" qrcode = { version = "^8.1", extras = ["pil"] } requests = "~2.32.3" +urllib3 = ">=2.6.0,<3" rlp = "^4.1.0" sd-jwt = "^0.10.3" unflatten = "~0.2" @@ -53,7 +54,7 @@ did-peer-4 = "^0.1.4" did-webvh = ">=1.0.0" # Verifiable Credentials -anoncreds = "~0.2.0" +anoncreds = "~0.2.3" indy-credx = "~1.1.1" # askar From 8797da139d89ab9d0d0faf6f757ec2f73b7e12fc Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Wed, 28 Jan 2026 10:03:39 -0500 Subject: [PATCH 2/2] formatting Signed-off-by: Patrick St-Louis --- acapy_agent/anoncreds/tests/test_issuer.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/acapy_agent/anoncreds/tests/test_issuer.py b/acapy_agent/anoncreds/tests/test_issuer.py index 1b0182a85d..0ed344ea65 100644 --- a/acapy_agent/anoncreds/tests/test_issuer.py +++ b/acapy_agent/anoncreds/tests/test_issuer.py @@ -391,14 +391,16 @@ async def test_create_and_register_schema_with_endorsed_transaction_response_doe async def test_finish_schema(self, mock_notify): # Create a mock entry with a valid schema JSON value mock_entry = mock.MagicMock() - mock_entry.value = json.dumps({ - "issuerId": "issuer-id", - "name": "test-schema", - "version": "1.0", - "attrNames": ["attr1", "attr2"] - }) + mock_entry.value = json.dumps( + { + "issuerId": "issuer-id", + "name": "test-schema", + "version": "1.0", + "attrNames": ["attr1", "attr2"], + } + ) mock_entry.tags = {} - + # Mock the transaction context manager mock_txn = mock.MagicMock() mock_handle = mock.MagicMock() @@ -409,11 +411,11 @@ async def test_finish_schema(self, mock_notify): mock_txn.commit = mock.CoroutineMock(return_value=None) mock_txn.__aenter__ = mock.CoroutineMock(return_value=mock_txn) mock_txn.__aexit__ = mock.CoroutineMock(return_value=None) - + self.profile.transaction = mock.Mock(return_value=mock_txn) - + await self.issuer.finish_schema(job_id="job-id", schema_id="schema-id") - + # Verify that SchemaFinishedEvent was emitted mock_notify.assert_called_once() call_args = mock_notify.call_args