From cda429fe96e8b6db3c1b55e36661215437a4d28e Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Fri, 15 Feb 2019 11:06:30 -0800 Subject: [PATCH 1/8] Configure flake8 to check docstrings Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/defaults.py | 2 +- agent/requirements.dev.txt | 7 +++++-- agent/setup.cfg | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/agent/indy_catalyst_agent/defaults.py b/agent/indy_catalyst_agent/defaults.py index 85805fd41d..db5beb84b9 100644 --- a/agent/indy_catalyst_agent/defaults.py +++ b/agent/indy_catalyst_agent/defaults.py @@ -37,7 +37,7 @@ def default_message_factory() -> MessageFactory: - """ """ + """Message factory for default message types.""" factory = MessageFactory() factory.register_message_types( diff --git a/agent/requirements.dev.txt b/agent/requirements.dev.txt index 39f5ecb90e..ccec059582 100644 --- a/agent/requirements.dev.txt +++ b/agent/requirements.dev.txt @@ -1,6 +1,9 @@ asynctest==0.12.2 pytest-asyncio==0.10.0 pytest-cov==2.6.1 -pytest-flake8===1.0.4 +pytest-flake8==1.0.4 -flake8==3.7.5 \ No newline at end of file +flake8==3.7.5 +flake8-rst-docstrings==0.0.8 +flake8-docstrings==1.3.0 +flake8-rst==0.7.1 \ No newline at end of file diff --git a/agent/setup.cfg b/agent/setup.cfg index 3922455d65..1943ad95bc 100644 --- a/agent/setup.cfg +++ b/agent/setup.cfg @@ -5,3 +5,5 @@ addopts = --cov-config .coveragerc --cov=indy_catalyst_agent --cov-report term - [flake8] # https://github.com/ambv/black#line-length max-line-length = 88 +exclude = + */tests/** \ No newline at end of file From c0357d1fb908ac081a681b863acde67d96d22c37 Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Tue, 19 Feb 2019 10:11:31 -0700 Subject: [PATCH 2/8] Add logging up to messages Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/logging/__init__.py | 14 +- .../messaging/agent_message.py | 203 ++++++++++++++-- .../messaging/base_handler.py | 12 + .../handlers/basicmessage_handler.py | 11 + .../messaging/basicmessage/message_types.py | 4 +- .../basicmessage/messages/basicmessage.py | 19 +- .../handlers/connection_invitation_handler.py | 12 +- .../handlers/connection_request_handler.py | 13 +- .../handlers/connection_response_handler.py | 11 +- .../messaging/connections/message_types.py | 4 +- .../messages/connection_invitation.py | 39 +++- .../messages/connection_request.py | 19 +- .../messages/connection_response.py | 18 +- .../credentials/messages/credential.py | 16 +- .../credentials/messages/credential_offer.py | 14 +- .../messages/credential_request.py | 16 +- .../messaging/message_factory.py | 36 ++- .../messaging/message_types.py | 4 +- .../messaging/proofs/messages/proof.py | 23 +- .../proofs/messages/proof_request.py | 22 +- .../messaging/request_context.py | 219 ++++++++++++++---- .../messaging/responder.py | 25 +- .../messaging/routing/messages/forward.py | 23 +- .../messaging/tests/test_validators.py | 18 -- .../messaging/tests/test_wire_message.py | 17 -- .../messaging/validators.py | 11 - .../messaging/wire_message.py | 21 -- agent/indy_catalyst_agent/wallet/__init__.py | 7 +- agent/setup.cfg | 3 +- 29 files changed, 634 insertions(+), 220 deletions(-) delete mode 100644 agent/indy_catalyst_agent/messaging/tests/test_validators.py delete mode 100644 agent/indy_catalyst_agent/messaging/tests/test_wire_message.py delete mode 100644 agent/indy_catalyst_agent/messaging/validators.py delete mode 100644 agent/indy_catalyst_agent/messaging/wire_message.py diff --git a/agent/indy_catalyst_agent/logging/__init__.py b/agent/indy_catalyst_agent/logging/__init__.py index 23f3d2fac2..3d6c2ec216 100644 --- a/agent/indy_catalyst_agent/logging/__init__.py +++ b/agent/indy_catalyst_agent/logging/__init__.py @@ -44,18 +44,20 @@ def print_banner( """ Print a startup banner describing the configuration. - :param inbound_transports: Configured inbound transports - :param outbound_transports: Configured outbound transports - :param banner_length: (Default value = 40) Length of the banner - :param border_character: (Default value = ":") Character to use in banner - border + Args: + inbound_transports: Configured inbound transports + outbound_transports: Configured outbound transports + banner_length: (Default value = 40) Length of the banner + border_character: (Default value = ":") Character to use in banner + border """ def lr_pad(content: str): """ Pad string content with defined border character. - :param content: String content to pad + Args: + content: String content to pad """ return ( f"{border_character}{border_character}" diff --git a/agent/indy_catalyst_agent/messaging/agent_message.py b/agent/indy_catalyst_agent/messaging/agent_message.py index a1364424c5..06303ae5f1 100644 --- a/agent/indy_catalyst_agent/messaging/agent_message.py +++ b/agent/indy_catalyst_agent/messaging/agent_message.py @@ -1,3 +1,5 @@ +"""Agent message base class and schema.""" + import uuid from typing import Dict @@ -23,7 +25,11 @@ class AgentMessage(BaseModel): + """Agent message base class.""" + class Meta: + """AgentMessage metadata.""" + handler_class = None schema_class = None message_type = None @@ -34,6 +40,18 @@ def __init__( _signatures: Dict[str, FieldSignature] = None, _thread: ThreadDecorator = None, ): + """ + Initialize base agent message object. + + Args: + _id: Agent message id + _signatures: Message signatures + _thread: ThreadDecorator object + + Raises: + TypeError: If message type is missing on subclass Meta class + + """ super(AgentMessage, self).__init__() self._message_id = _id or str(uuid.uuid4()) self._message_thread = _thread @@ -52,53 +70,106 @@ def __init__( @classmethod def _get_handler_class(cls): - """ """ + """ + Get handler class. + + Returns: + The resolved class defined on `Meta.handler_class` + + """ return resolve_class(cls.Meta.handler_class, cls) @property def Handler(self) -> type: - """Accessor for the agent message's handler class""" + """ + Accessor for the agent message's handler class. + + Returns: + Handler class + + """ return self._get_handler_class() @property def _type(self) -> str: - """Accessor for the message type identifier""" + """ + Accessor for the message type identifier. + + Returns: + Message type defined on `Meta.message_type` + + """ return self.Meta.message_type @property def _id(self) -> str: - """Accessor for the unique message identifier""" + """ + Accessor for the unique message identifier. + + Returns: + The id of this message + + """ return self._message_id @_id.setter def _id(self, val: str): - """ - Set the unique message identifier - """ + """Set the unique message identifier.""" self._message_id = val @property def _signatures(self) -> Dict[str, FieldSignature]: - """Fetch the dictionary of defined field signatures""" + """ + Fetch the dictionary of defined field signatures. + + Returns: + A copy of the message_signatures for this message. + + """ return self._message_signatures.copy() def get_signature(self, field_name: str) -> FieldSignature: """ - Get the signature for a named field + Get the signature for a named field. + + Args: + field_name: Field name to get signatures for + + Returns: + A FieldSignature for the requested field name + """ return self._message_signatures.get(field_name) def set_signature(self, field_name: str, signature: FieldSignature): """ - Add or replace the signature for a named field + Add or replace the signature for a named field. + + Args: + field_name: Field to set signature on + signature: Signature for the field + """ self._message_signatures[field_name] = signature async def sign_field( - self, field_name: str, signer: str, wallet: BaseWallet, timestamp=None + self, field_name: str, signer_verkey: str, wallet: BaseWallet, timestamp=None ) -> FieldSignature: """ - Create and store a signature for a named field + Create and store a signature for a named field. + + Args: + field_name: Field to sign + signer_verkey: Verkey of signer + wallet: Wallet to use for signature + timestamp: Optional timestamp for signature + + Returns: + A FieldSignature for newly created signature + + Raises: + ValueError: If field_name doesn't exist on this message + """ value = getattr(self, field_name, None) if value is None: @@ -107,17 +178,30 @@ async def sign_field( self.__class__.__name__, field_name ) ) - sig = await FieldSignature.create(value, signer, wallet, timestamp) + sig = await FieldSignature.create(value, signer_verkey, wallet, timestamp) self.set_signature(field_name, sig) return sig async def verify_signed_field( - self, field_name: str, wallet: BaseWallet, signer: str = None + self, field_name: str, wallet: BaseWallet, signer_verkey: str = None ) -> str: """ - Verify a specific field signature + Verify a specific field signature. + + Args: + field_name: The field name to verify + wallet: Wallet to use for the verification + signer_verkey: Verkey of signer to use + + Returns: + The verkey of the signer + + Raises: + ValueError: If field_name does not exist on this message + ValueError: If the verification fails + ValueError: If the verkey of the signature does not match the + provided verkey - Returns: the verkey of the signer """ if field_name not in self._message_signatures: raise ValueError("Missing field signature: {}".format(field_name)) @@ -126,15 +210,21 @@ async def verify_signed_field( raise ValueError( "Field signature verification failed: {}".format(field_name) ) - if signer is not None and sig.signer != signer: + if signer_verkey is not None and sig.signer != signer_verkey: raise ValueError( - "Signer of signature does not match: {}".format(field_name) + "Signer verkey of signature does not match: {}".format(field_name) ) return sig.signer async def verify_signatures(self, wallet: BaseWallet) -> bool: """ - Verify all associated field signatures + Verify all associated field signatures. + + Args: + wallet: Wallet to use in verification + + Returns: + True if all signatures verify, else false """ for sig in self._message_signatures.values(): if not await sig.verify(wallet): @@ -143,19 +233,32 @@ async def verify_signatures(self, wallet: BaseWallet) -> bool: @property def _thread(self) -> ThreadDecorator: - """Accessor for the message's thread decorator""" + """ + Accessor for the message's thread decorator. + + Returns: + The ThreadDecorator for this message + + """ return self._message_thread @_thread.setter def _thread(self, val: ThreadDecorator): """ - Setter for the message's thread decorator + Setter for the message's thread decorator. + + Args: + val: ThreadDecorator to set as the thread """ self._message_thread = val class AgentMessageSchema(BaseModelSchema): + """AgentMessage schema.""" + class Meta: + """AgentMessageSchema metadata.""" + model_class = None signed_fields = None @@ -167,6 +270,13 @@ class Meta: _thread = fields.Nested(ThreadDecoratorSchema, data_key="~thread", required=False) def __init__(self, *args, **kwargs): + """ + Initialize an instance of AgentMessageSchema. + + Raises: + TypeError: If Meta.model_class has not been set + + """ super(AgentMessageSchema, self).__init__(*args, **kwargs) if not self.Meta.model_class: raise TypeError( @@ -178,6 +288,24 @@ def __init__(self, *args, **kwargs): @pre_load def parse_signed_fields(self, data): + """ + Pre-load hook to parse all of the signed fields. + + Args: + data: Incoming data to parse + + Returns: + Parsed and modified data + + Raises: + ValidationError: If the field name prefix does not exist + ValidationError: If the field signature does not correlate + to a field in the message + ValidationError: If the message defines both a field signature + and a value + ValidationError: If there is a missing field signature + + """ expect_fields = resolve_meta_property(self, "signed_fields") or () found = {} for field_name, field_value in data.items(): @@ -205,12 +333,35 @@ def parse_signed_fields(self, data): @post_load def populate_signatures(self, obj): + """ + Post-load hook to populate signatures on the message. + + Args: + obj: The AgentMessage object + + Returns: + The AgentMessage object with populated signatures + + """ for field_name, sig in self._signatures.items(): obj.set_signature(field_name, sig) return obj @pre_dump def copy_signatures(self, obj): + """ + Pre-dump hook to copy the message signatures into the serialized output. + + Args: + obj: The AgentMessage object + + Returns: + The modified object + + Raises: + ValidationError: If a signature is missing + + """ self._signatures = obj._signatures expect_fields = resolve_meta_property(self, "signed_fields") or () for field_name in expect_fields: @@ -222,6 +373,16 @@ def copy_signatures(self, obj): @post_dump def replace_signatures(self, data): + """ + Post-dump hook to write the signatures to the serialized output. + + Args: + obj: The serialized data + + Returns: + The modified data + + """ for field_name, sig in self._signatures.items(): del data[field_name] data["{}~sig".format(field_name)] = sig.serialize() diff --git a/agent/indy_catalyst_agent/messaging/base_handler.py b/agent/indy_catalyst_agent/messaging/base_handler.py index ef6e62775c..52f975ee1c 100644 --- a/agent/indy_catalyst_agent/messaging/base_handler.py +++ b/agent/indy_catalyst_agent/messaging/base_handler.py @@ -1,3 +1,5 @@ +"""A Base handler class for all message handlers.""" + from abc import ABC, abstractmethod import logging @@ -7,9 +9,19 @@ class BaseHandler(ABC): """Abstract base class for handlers.""" + def __init__(self) -> None: + """Initialize a BaseHandler instance.""" self._logger = logging.getLogger(__name__) @abstractmethod async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Abstract method for handler logic. + + Args: + context: Request context object + responder: A responder object + + """ pass diff --git a/agent/indy_catalyst_agent/messaging/basicmessage/handlers/basicmessage_handler.py b/agent/indy_catalyst_agent/messaging/basicmessage/handlers/basicmessage_handler.py index 5af38e5cdf..316c413190 100644 --- a/agent/indy_catalyst_agent/messaging/basicmessage/handlers/basicmessage_handler.py +++ b/agent/indy_catalyst_agent/messaging/basicmessage/handlers/basicmessage_handler.py @@ -1,9 +1,20 @@ +"""Basic message handler.""" + from ...base_handler import BaseHandler, BaseResponder, RequestContext from ..messages.basicmessage import BasicMessage class BasicMessageHandler(BaseHandler): + """Message handler class for basic messages.""" + async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Message handler logic for basic messages. + + Args: + context: request context + responder: responder callback + """ self._logger.debug(f"BasicMessageHandler called with context {context}") assert isinstance(context.message, BasicMessage) diff --git a/agent/indy_catalyst_agent/messaging/basicmessage/message_types.py b/agent/indy_catalyst_agent/messaging/basicmessage/message_types.py index 3d5003520c..8e3226ef09 100644 --- a/agent/indy_catalyst_agent/messaging/basicmessage/message_types.py +++ b/agent/indy_catalyst_agent/messaging/basicmessage/message_types.py @@ -1,6 +1,4 @@ -""" -Message type identifiers for Connections -""" +"""Message type identifiers for Connections.""" MESSAGE_FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/basicmessage/1.0/" diff --git a/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py b/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py index f2eea24949..7ef255c938 100644 --- a/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py +++ b/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py @@ -1,6 +1,4 @@ -""" -Represents an invitation message for establishing connection. -""" +"""Basic message.""" from datetime import datetime, timezone from typing import Union @@ -17,7 +15,11 @@ class BasicMessage(AgentMessage): + """Class defining the structure of a basic message.""" + class Meta: + """Basic message metadata class.""" + handler_class = HANDLER_CLASS message_type = BASIC_MESSAGE schema_class = "BasicMessageSchema" @@ -25,6 +27,13 @@ class Meta: def __init__( self, *, sent_time: Union[str, datetime] = None, content: str = None, **kwargs ): + """ + Initialize basic message object. + + Args: + sent_time: Time message was sent + content: message content + """ super(BasicMessage, self).__init__(**kwargs) if not sent_time: sent_time = datetime.utcnow() @@ -35,7 +44,11 @@ def __init__( class BasicMessageSchema(AgentMessageSchema): + """Basic message schema class.""" + class Meta: + """Basic message schema metadata.""" + model_class = BasicMessage sent_time = fields.Str(required=False) diff --git a/agent/indy_catalyst_agent/messaging/connections/handlers/connection_invitation_handler.py b/agent/indy_catalyst_agent/messaging/connections/handlers/connection_invitation_handler.py index ab689e9743..a9bd4fe6ea 100644 --- a/agent/indy_catalyst_agent/messaging/connections/handlers/connection_invitation_handler.py +++ b/agent/indy_catalyst_agent/messaging/connections/handlers/connection_invitation_handler.py @@ -1,12 +1,22 @@ +"""Connect invitation handler.""" + from ...base_handler import BaseHandler, BaseResponder, RequestContext from ..messages.connection_invitation import ConnectionInvitation from ....connection import ConnectionManager class ConnectionInvitationHandler(BaseHandler): - """Handler for connection invitations.""" + """Handler class for connection invitations.""" async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Handle connection invitation. + + Args: + context: Request context + responder: Responder callback + """ + self._logger.debug(f"ConnectionInvitationHandler called with context {context}") assert isinstance(context.message, ConnectionInvitation) diff --git a/agent/indy_catalyst_agent/messaging/connections/handlers/connection_request_handler.py b/agent/indy_catalyst_agent/messaging/connections/handlers/connection_request_handler.py index d3702ec3b7..95bdb618b3 100644 --- a/agent/indy_catalyst_agent/messaging/connections/handlers/connection_request_handler.py +++ b/agent/indy_catalyst_agent/messaging/connections/handlers/connection_request_handler.py @@ -1,11 +1,22 @@ +"""Connection request handler.""" + from ...base_handler import BaseHandler, BaseResponder, RequestContext from ..messages.connection_request import ConnectionRequest from ....connection import ConnectionManager class ConnectionRequestHandler(BaseHandler): - """ """ + """Handler class for connection requests.""" + async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Handle connection request. + + Args: + context: Request context + responder: Responder callback + """ + self._logger.debug(f"ConnectionRequestHandler called with context {context}") assert isinstance(context.message, ConnectionRequest) diff --git a/agent/indy_catalyst_agent/messaging/connections/handlers/connection_response_handler.py b/agent/indy_catalyst_agent/messaging/connections/handlers/connection_response_handler.py index 2831580d6b..f7eb707ac4 100644 --- a/agent/indy_catalyst_agent/messaging/connections/handlers/connection_response_handler.py +++ b/agent/indy_catalyst_agent/messaging/connections/handlers/connection_response_handler.py @@ -1,12 +1,21 @@ +"""Connection response handler.""" + from ...base_handler import BaseHandler, BaseResponder, RequestContext from ..messages.connection_response import ConnectionResponse from ....connection import ConnectionManager class ConnectionResponseHandler(BaseHandler): - """ """ + """Handler class for connection responses.""" async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Handle connection response. + + Args: + context: Request context + responder: Responder callback + """ self._logger.debug(f"ConnectionResponseHandler called with context {context}") assert isinstance(context.message, ConnectionResponse) diff --git a/agent/indy_catalyst_agent/messaging/connections/message_types.py b/agent/indy_catalyst_agent/messaging/connections/message_types.py index 70c293c334..c0d6da021c 100644 --- a/agent/indy_catalyst_agent/messaging/connections/message_types.py +++ b/agent/indy_catalyst_agent/messaging/connections/message_types.py @@ -1,6 +1,4 @@ -""" -Message type identifiers for Connections -""" +"""Message type identifiers for Connections.""" MESSAGE_FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/" diff --git a/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py b/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py index 0463257fff..395a5e11c3 100644 --- a/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py +++ b/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py @@ -21,6 +21,8 @@ class ConnectionInvitation(AgentMessage): """Class representing a connection invitation.""" class Meta: + """Metadata for a connection invitation.""" + handler_class = HANDLER_CLASS message_type = CONNECTION_INVITATION schema_class = "ConnectionInvitationSchema" @@ -35,6 +37,16 @@ def __init__( routing_keys: Sequence[str] = None, **kwargs, ): + """ + Initialize connection invitation object. + + Args: + label: Optional label for connection + did: DID for this connection invitation + recipient_keys: List of recipient keys + endpoint: Endpoint which this agent can be reached at + routing_keys: List of routing keys + """ super(ConnectionInvitation, self).__init__(**kwargs) self.label = label self.did = did @@ -44,7 +56,11 @@ def __init__( def to_url(self) -> str: """ - Convert an invitation to URL format for sharing + Convert an invitation to URL format for sharing. + + Returns: + An invite url + """ c_json = self.to_json() c_i = bytes_to_b64(c_json.encode("ascii"), urlsafe=True) @@ -54,7 +70,14 @@ def to_url(self) -> str: @classmethod def from_url(cls, url: str) -> "ConnectionInvitation": """ - Parse a URL-encoded invitation into a `ConnectionInvitation` message + Parse a URL-encoded invitation into a `ConnectionInvitation` message. + + Args: + url: Url to decode + + Returns: + A `ConnectionInvitation` object. + """ parts = urlparse(url) query = parse_qs(parts.query) @@ -65,9 +88,11 @@ def from_url(cls, url: str) -> "ConnectionInvitation": class ConnectionInvitationSchema(AgentMessageSchema): - """Class """ + """Connection invitation schema class.""" class Meta: + """Connection invitation schema metadata.""" + model_class = ConnectionInvitation label = fields.Str() @@ -80,7 +105,13 @@ class Meta: def validate_fields(self, data): """ Validate schema fields. - :param data: + + Args: + data: The data to validate + + Raises: + ValidationError: If any of the fields do not validate + """ if data.get("did"): if data.get("recipient_keys"): diff --git a/agent/indy_catalyst_agent/messaging/connections/messages/connection_request.py b/agent/indy_catalyst_agent/messaging/connections/messages/connection_request.py index 7973af6327..d8b3f71cdb 100644 --- a/agent/indy_catalyst_agent/messaging/connections/messages/connection_request.py +++ b/agent/indy_catalyst_agent/messaging/connections/messages/connection_request.py @@ -1,6 +1,4 @@ -""" -Represents a connection request message. -""" +"""Represents a connection request message.""" from marshmallow import fields @@ -15,10 +13,10 @@ class ConnectionRequest(AgentMessage): - """ """ + """Class representing a connection request.""" class Meta: - """ """ + """Metadata for a connection request.""" handler_class = HANDLER_CLASS message_type = CONNECTION_REQUEST @@ -27,16 +25,23 @@ class Meta: def __init__( self, *, connection: ConnectionDetail = None, label: str = None, **kwargs ): + """ + Initialize connection request object. + + Args: + connection (ConnectionDetail): Connection details object + label: Label for this connection request + """ super(ConnectionRequest, self).__init__(**kwargs) self.connection = connection self.label = label class ConnectionRequestSchema(AgentMessageSchema): - """ """ + """Connection request schema class.""" class Meta: - """ """ + """Connection request schema metadata.""" model_class = ConnectionRequest diff --git a/agent/indy_catalyst_agent/messaging/connections/messages/connection_response.py b/agent/indy_catalyst_agent/messaging/connections/messages/connection_response.py index f732636405..4efc4e90e2 100644 --- a/agent/indy_catalyst_agent/messaging/connections/messages/connection_response.py +++ b/agent/indy_catalyst_agent/messaging/connections/messages/connection_response.py @@ -1,6 +1,4 @@ -""" -Represents a connection response message -""" +"""Represents a connection response message.""" from marshmallow import fields @@ -15,25 +13,31 @@ class ConnectionResponse(AgentMessage): - """ """ + """Class representing a connection response.""" class Meta: - """ """ + """Metadata for a connection response.""" handler_class = HANDLER_CLASS schema_class = "ConnectionResponseSchema" message_type = CONNECTION_RESPONSE def __init__(self, *, connection: ConnectionDetail = None, **kwargs): + """ + Initialize connection response object. + + Args: + connection: Connection details object + """ super(ConnectionResponse, self).__init__(**kwargs) self.connection = connection class ConnectionResponseSchema(AgentMessageSchema): - """ """ + """Connection response schema class.""" class Meta: - """ """ + """Connection response schema metadata.""" model_class = ConnectionResponse signed_fields = ("connection",) diff --git a/agent/indy_catalyst_agent/messaging/credentials/messages/credential.py b/agent/indy_catalyst_agent/messaging/credentials/messages/credential.py index 0be6c77d6d..282c60a4a6 100644 --- a/agent/indy_catalyst_agent/messaging/credentials/messages/credential.py +++ b/agent/indy_catalyst_agent/messaging/credentials/messages/credential.py @@ -1,6 +1,4 @@ -""" -A credential content message. -""" +"""A credential content message.""" from marshmallow import fields @@ -12,6 +10,8 @@ class Credential(AgentMessage): """Class representing a credential.""" class Meta: + """Credential metadata.""" + # handler_class = CredentialHandler schema_class = "CredentialSchema" message_type = MessageTypes.CREDENTIAL.value @@ -23,6 +23,13 @@ def __init__( revocation_registry_id: str = None, **kwargs ): + """ + Initialize credential object. + + Args: + credential_json (str): Credential offer json string + revocation_registry_id (str): Credential registry id + """ super(Credential, self).__init__(**kwargs) self.credential_json = credential_json self.revocation_registry_id = revocation_registry_id @@ -30,7 +37,10 @@ def __init__( class CredentialSchema(AgentMessageSchema): """Credential schema.""" + class Meta: + """Credential schema metadata.""" + model_class = Credential credential_json = fields.Str(required=True) diff --git a/agent/indy_catalyst_agent/messaging/credentials/messages/credential_offer.py b/agent/indy_catalyst_agent/messaging/credentials/messages/credential_offer.py index e4a8418380..2e313b8e7d 100644 --- a/agent/indy_catalyst_agent/messaging/credentials/messages/credential_offer.py +++ b/agent/indy_catalyst_agent/messaging/credentials/messages/credential_offer.py @@ -1,6 +1,4 @@ -""" -A credential offer content message. -""" +"""A credential offer content message.""" from marshmallow import fields @@ -12,11 +10,19 @@ class CredentialOffer(AgentMessage): """Class representing a credential offer.""" class Meta: + """CredentialOffer metadata.""" + # handler_class = CredentialOfferHandler schema_class = "CredentialOfferSchema" message_type = MessageTypes.CREDENTIAL_OFFER.value def __init__(self, *, offer_json: str = None, **kwargs): + """ + Initialize credential offer object. + + Args: + offer_json (str): Credential offer json + """ super(CredentialOffer, self).__init__(**kwargs) self.offer_json = offer_json @@ -25,6 +31,8 @@ class CredentialOfferSchema(AgentMessageSchema): """Credential offer schema.""" class Meta: + """Credential offer schema metadata.""" + model_class = CredentialOffer offer_json = fields.Str(required=True) diff --git a/agent/indy_catalyst_agent/messaging/credentials/messages/credential_request.py b/agent/indy_catalyst_agent/messaging/credentials/messages/credential_request.py index 26ca1420fe..c30bb0f0e8 100644 --- a/agent/indy_catalyst_agent/messaging/credentials/messages/credential_request.py +++ b/agent/indy_catalyst_agent/messaging/credentials/messages/credential_request.py @@ -1,6 +1,4 @@ -""" -A credential request content message. -""" +"""A credential request content message.""" from marshmallow import fields @@ -12,6 +10,8 @@ class CredentialRequest(AgentMessage): """Class representing a credential request.""" class Meta: + """CredentialRequest metadata.""" + # handler_class = CredentialRequestHandler schema_class = "CredentialRequestSchema" message_type = MessageTypes.CREDENTIAL_REQUEST.value @@ -24,6 +24,14 @@ def __init__( credential_values_json: str = None, **kwargs ): + """ + Initialize credential request object. + + Args: + offer_json (str): Credential offer json string + credential_request_json: Credential request json string + credential_values_json: Credential values json string + """ super(CredentialRequest, self).__init__(**kwargs) self.offer_json = offer_json self.credential_request_json = credential_request_json @@ -34,6 +42,8 @@ class CredentialRequestSchema(AgentMessageSchema): """Credential request schema.""" class Meta: + """Credential request schema metadata.""" + model_class = CredentialRequest offer_json = fields.Str(required=True) diff --git a/agent/indy_catalyst_agent/messaging/message_factory.py b/agent/indy_catalyst_agent/messaging/message_factory.py index 486259ef36..ee57efbd37 100644 --- a/agent/indy_catalyst_agent/messaging/message_factory.py +++ b/agent/indy_catalyst_agent/messaging/message_factory.py @@ -1,6 +1,4 @@ -""" -Handle identification of message types and instantiation of message classes -""" +"""Handle identification of message types and instantiation of message classes.""" from ..classloader import ClassLoader from ..error import BaseError @@ -15,19 +13,18 @@ class MessageParseError(BaseError): class MessageFactory: - """ - Message factory for deserializing message json and obtaining relevant - message class - """ + """Message factory for deserializing messages.""" def __init__(self): + """Initialize a MessageFactory instance.""" self._typemap = {} def register_message_types(self, *types): """ Add new supported message types. - :param *types: + Args: + *types: Message types to register """ for typeset in types: @@ -35,10 +32,17 @@ def register_message_types(self, *types): def resolve_message_class(self, message_type: str) -> type: """ + Resolve a message_type to a message class. + Given a dict describing a message, this method returns the corresponding registered message class. - :param message_type: str: + Args: + message_type: Message type to resolve + + Returns: + The resolved message class + """ msg_cls = self._typemap.get(message_type) if isinstance(msg_cls, str): @@ -47,10 +51,21 @@ def resolve_message_class(self, message_type: str) -> type: def make_message(self, serialized_msg: dict) -> AgentMessage: """ + Desererialize a message dict into a relevant message instance. + Given a dict describing a message, this method returns an instance of the related message class. - :param serialized_msg: dict: + Args: + serialized_msg: The serialized message + + Returns: + An instance of the corresponding message class for this message + + Raises: + MessageParseError: If the message doesn't specify @type + MessageParseError: If there is no message class registered to handle + the given type """ @@ -66,4 +81,5 @@ def make_message(self, serialized_msg: dict) -> AgentMessage: return instance def __repr__(self) -> str: + """Return a string representation for this class.""" return "<{}>".format(self.__class__.__name__) diff --git a/agent/indy_catalyst_agent/messaging/message_types.py b/agent/indy_catalyst_agent/messaging/message_types.py index da37f0d5f7..d20a7ac0b2 100644 --- a/agent/indy_catalyst_agent/messaging/message_types.py +++ b/agent/indy_catalyst_agent/messaging/message_types.py @@ -1,8 +1,10 @@ +"""Message types for some built in message families.""" + from enum import Enum class MessageTypes(Enum): - """ """ + """Enum of built in message types.""" # Connection Messages CONNECTION_INVITATION = ( diff --git a/agent/indy_catalyst_agent/messaging/proofs/messages/proof.py b/agent/indy_catalyst_agent/messaging/proofs/messages/proof.py index 3b83ad4e41..98e7663193 100644 --- a/agent/indy_catalyst_agent/messaging/proofs/messages/proof.py +++ b/agent/indy_catalyst_agent/messaging/proofs/messages/proof.py @@ -1,6 +1,4 @@ -""" -A proof content message. -""" +"""A proof content message.""" from marshmallow import fields @@ -9,23 +7,34 @@ class Proof(AgentMessage): - """ """ + """Class representing a proof.""" + class Meta: - """ """ + """Proof metadata.""" + # handler_class = ProofHandler schema_class = "ProofSchema" message_type = MessageTypes.PROOF.value def __init__(self, proof_json: str = None, request_nonce: str = None, **kwargs): + """ + Initialize proof object. + + Args: + proof_json (str): Proof json string + request_nonce (str): Proof request nonce + """ super(Proof, self).__init__(**kwargs) self.proof_json = proof_json self.request_nonce = request_nonce class ProofSchema(AgentMessageSchema): - """ """ + """Proof schema.""" + class Meta: - """ """ + """ProofSchema metadata.""" + model_class = Proof # Avoid clobbering builtin property diff --git a/agent/indy_catalyst_agent/messaging/proofs/messages/proof_request.py b/agent/indy_catalyst_agent/messaging/proofs/messages/proof_request.py index 1e3d3d6175..8d4a9a57fd 100644 --- a/agent/indy_catalyst_agent/messaging/proofs/messages/proof_request.py +++ b/agent/indy_catalyst_agent/messaging/proofs/messages/proof_request.py @@ -1,6 +1,4 @@ -""" -A proof request content message. -""" +"""A proof request content message.""" from marshmallow import fields @@ -9,22 +7,32 @@ class ProofRequest(AgentMessage): - """ """ + """Class representing a proof request.""" + class Meta: - """ """ + """ProofRequest metadata.""" + # handler_class = ProofRequestHandler message_type = MessageTypes.PROOF_REQUEST.value schema_class = "ProofRequestSchema" def __init__(self, proof_request_json: str = None, **kwargs): + """ + Initialize proof request object. + + Args: + proof_request_json (str): Proof request json string + """ super(ProofRequest, self).__init__(**kwargs) self.proof_request_json = proof_request_json class ProofRequestSchema(AgentMessageSchema): - """ """ + """ProofRequest schema.""" + class Meta: - """ """ + """ProofRequestSchema metadata.""" + model_class = ProofRequest proof_request_json = fields.Str(required=True) diff --git a/agent/indy_catalyst_agent/messaging/request_context.py b/agent/indy_catalyst_agent/messaging/request_context.py index ae676e0161..93dfc564ed 100644 --- a/agent/indy_catalyst_agent/messaging/request_context.py +++ b/agent/indy_catalyst_agent/messaging/request_context.py @@ -1,5 +1,8 @@ """ -Request context class +Request context class. + +A request context provides everything required by handlers and other parts +of the system to process a message. """ import copy @@ -16,9 +19,10 @@ class RequestContext: - """Context established by the Conductor and passed into message handlers""" + """Context established by the Conductor and passed into message handlers.""" def __init__(self): + """Initialize an instance of RequestContext.""" self._default_endpoint = None self._default_label = None self._logger = logging.getLogger(__name__) @@ -33,61 +37,98 @@ def __init__(self): self._wallet = None def copy(self) -> "RequestContext": - """Create a copy of this context""" + """ + Create a copy of this context. + + Returns: + A copy of this instance + + """ return copy.copy(self) @property def default_endpoint(self) -> str: - """Accessor for the default agent endpoint (from agent config)""" + """ + Accessor for the default agent endpoint (from agent config). + + Returns: + The default agent endpoint + + """ return self._default_endpoint @default_endpoint.setter - def default_endpoint(self, endp: str): - """Setter for the default agent endpoint (from agent config) + def default_endpoint(self, endpoint: str): + """ + Setter for the default agent endpoint (from agent config). - :param endp: str: + Args: + endpoint: The new default endpoint """ - self._default_endpoint = endp + self._default_endpoint = endpoint @property def default_label(self) -> str: - """Accessor for the default agent label (from agent config)""" + """ + Accessor for the default agent label (from agent config). + + Returns: + The default label + + """ return self._default_label @default_label.setter - def default_label(self, lbl: str): - """Setter for the default agent label (from agent config) + def default_label(self, label: str): + """ + Setter for the default agent label (from agent config). - :param lbl: str: + Args: + label: The new default label """ - self._default_label = lbl + self._default_label = label @property def recipient_verkey(self) -> str: - """Accessor for the recipient public key used to pack the incoming request""" + """ + Accessor for the recipient verkey key used to pack the incoming request. + + Returns: + The recipient verkey + + """ return self._recipient_verkey @recipient_verkey.setter def recipient_verkey(self, verkey: str): - """Setter for the recipient public key used to pack the incoming request - - :param verkey: str: + """ + Setter for the recipient public key used to pack the incoming request. + Args: + verkey: The new recipient verkey """ self._recipient_verkey = verkey @property def recipient_did(self) -> str: - """Accessor for the recipient DID which corresponds with the verkey""" + """ + Accessor for the recipient DID which corresponds with the verkey. + + Returns: + The recipient DID + + """ return self._recipient_did @recipient_did.setter def recipient_did(self, did: str): - """Setter for the recipient DID which corresponds with the verkey + """ + Setter for the recipient DID which corresponds with the verkey. - :param did: str: + Args: + did: The new recipient DID """ self._recipient_did = did @@ -95,108 +136,165 @@ def recipient_did(self, did: str): @property def recipient_did_public(self) -> bool: """ - Indicates whether the message is associated with a public (ledger) recipient DID + Check if the recipient did is public. + + Indicates whether the message is associated with + a public (ledger) recipient DID. + + Returns: + True if the recipient's DID is public, else false + """ return self._recipient_did_public @recipient_did_public.setter def recipient_did_public(self, public: bool): - """Setter for the flag indicating the recipient DID is public + """ + Setter for the flag indicating the recipient DID is public. - :param public: bool: + Args: + public: A boolean value to indicate if the recipient DID is public """ self._recipient_did_public = public @recipient_verkey.setter def recipient_verkey(self, verkey: str): - """Setter for the recipient public key used to pack the incoming request + """ + Setter for the recipient public key used to pack the incoming request. - :param verkey: str: + Args: + verkey: This context's recipient's verkey """ self._recipient_verkey = verkey @property def sender_verkey(self) -> str: - """Accessor for the sender public key used to pack the incoming request""" + """ + Accessor for the sender public key used to pack the incoming request. + + Returns: + This context's sender's verkey + + """ return self._sender_verkey @sender_verkey.setter def sender_verkey(self, verkey: str): - """Setter for the sender public key used to pack the incoming request + """ + Setter for the sender public key used to pack the incoming request. - :param verkey: str: + Args: + verkey: This context's sender's verkey """ self._sender_verkey = verkey @property def transport_type(self) -> str: - """Accessor for the transport type used to receive the message""" + """ + Accessor for the transport type used to receive the message. + + Returns: + This context's transport type + + """ return self._transport_type @transport_type.setter def transport_type(self, transport: str): - """Setter for the transport type used to receive the message + """ + Setter for the transport type used to receive the message - :param transport: str: + Args: + transport: This context's new transport """ self._transport_type = transport @property def message_factory(self) -> MessageFactory: - """Accessor for the message factory instance""" + """ + Accessor for the message factory instance. + + Returns: + This context's message factory + + """ return self._message_factory @message_factory.setter def message_factory(self, factory: MessageFactory): - """Setter for the message factory instance + """ + Setter for the message factory instance. - :param factory: MessageFactory: + Args: + factory: This context's new message factory """ self._message_factory = factory @property def message(self) -> AgentMessage: - """Accessor for the deserialized message instance""" + """ + Accessor for the deserialized message instance. + + Returns: + This context's agent message + + """ return self._message @message.setter def message(self, msg: AgentMessage): - """Setter for the deserialized message instance - - :param msg: AgentMessage: + """ + Setter for the deserialized message instance. + Args: + msg: This context's new agent message """ self._message = msg @property def storage(self) -> BaseStorage: - """Accessor for the BaseStorage implementation""" + """ + Accessor for the BaseStorage implementation. + + Returns: + This context's storage implementation + + """ return self._storage @storage.setter def storage(self, storage: BaseStorage): - """Setter for the BaseStorage implementation - - :param storage: BaseStorage: + """ + Setter for the BaseStorage implementation. + Args: + storage: This context's new storage driver """ self._storage = storage @property def wallet(self) -> BaseWallet: - """Accessor for the BaseWallet implementation""" + """ + Accessor for the BaseWallet implementation. + + Returns: + This context's wallet implementation + + """ return self._wallet @wallet.setter def wallet(self, wallet: BaseWallet): - """Setter for the BaseWallet implementation + """ + Setter for the BaseWallet implementation. - :param wallet: BaseWallet: + Args: + wallet: This context's new wallet implementation """ self._wallet = wallet @@ -204,7 +302,20 @@ async def expand_message( self, message_body: Union[str, bytes], transport_type: str ) -> "RequestContext": """ - Deserialize an incoming message + Deserialize an incoming message. + + Args: + message_body: The body of the incoming message + transport_type: The transport type of this message + + Returns: + The updated context + + Raises: + MessageParseError: If there is no message factory on the context + MessageParseError: If there is no wallet on the context + MessageParseError: If contents of the message json cannot be parsed + """ if not self.message_factory: raise MessageParseError("Message factory not defined") @@ -259,7 +370,15 @@ async def compact_message( self, message: AgentMessage, target: ConnectionTarget ) -> Union[str, bytes]: """ - Serialize an outgoing message for transport + Serialize an outgoing message for transport. + + Args: + message: The agent message to serialize + target: The connection target to compact for + + Returns: + The serialized message + """ message_dict = message.serialize() message_json = json.dumps(message_dict) @@ -278,6 +397,12 @@ async def compact_message( # - Extra transport info? (received at endpoint?) def __repr__(self) -> str: + """ + Provide a human readable representation of this object. + + Returns: + A human readable representation of this object + """ skip = ("_logger",) items = ( "{}={}".format(k, repr(v)) diff --git a/agent/indy_catalyst_agent/messaging/responder.py b/agent/indy_catalyst_agent/messaging/responder.py index 4a27bc4303..be0c5862fe 100644 --- a/agent/indy_catalyst_agent/messaging/responder.py +++ b/agent/indy_catalyst_agent/messaging/responder.py @@ -1,3 +1,10 @@ +""" +A message responder. + +The responder is provided to message handlers to enable them to send a new message +in response to the message being handled. +""" + from abc import ABC from .agent_message import AgentMessage @@ -12,23 +19,39 @@ class ResponderError(BaseError): class BaseResponder(ABC): - """Interface for message handlers to send responses""" + """Interface for message handlers to send responses.""" async def send_outbound(self, message: AgentMessage, target: ConnectionTarget): """ + Send outbound message. + Send a message to a given connection target (endpoint). The message may be queued for later delivery. + + Args: + message: AgentMessage to be sent + target: ConnectionTarget to send this message to """ async def send_reply(self, message: AgentMessage): """ + Send message as reply. + Send a message back to the same agent. This relies on the presence of an active connection. The message may be multicast to multiple endpoints or queued for later delivery. + + Args: + message: AgentMessage to be sent """ async def send_admin_message(self, message: AgentMessage): """ + Send admin message. + Send an admin message to active listeners. + + Args: + message: AgentMessage to be sent """ diff --git a/agent/indy_catalyst_agent/messaging/routing/messages/forward.py b/agent/indy_catalyst_agent/messaging/routing/messages/forward.py index e9de2180e6..ab68ea22e7 100644 --- a/agent/indy_catalyst_agent/messaging/routing/messages/forward.py +++ b/agent/indy_catalyst_agent/messaging/routing/messages/forward.py @@ -1,6 +1,4 @@ -""" -Represents a forward message. -""" +"""Represents a forward message.""" from marshmallow import fields @@ -9,23 +7,34 @@ class Forward(AgentMessage): - """ """ + """Class representing a forward message.""" + class Meta: - """ """ + """Forward metadata.""" + # handler_class = ForwardHandler message_type = MessageTypes.FORWARD.value schema_class = "ForwardSchema" def __init__(self, to: str = None, msg: str = None, **kwargs): + """ + Initialize forward message object. + + Args: + to (str): Recipient DID + msg (str): Message content + """ super(Forward, self).__init__(**kwargs) self.to = to self.msg = msg class ForwardSchema(AgentMessageSchema): - """ """ + """Forward schema.""" + class Meta: - """ """ + """ForwardSchema metadata.""" + model_class = Forward to = fields.Str(required=True) diff --git a/agent/indy_catalyst_agent/messaging/tests/test_validators.py b/agent/indy_catalyst_agent/messaging/tests/test_validators.py deleted file mode 100644 index d3d11fe444..0000000000 --- a/agent/indy_catalyst_agent/messaging/tests/test_validators.py +++ /dev/null @@ -1,18 +0,0 @@ -from ..validators import must_not_be_none - -from marshmallow import ValidationError - -from unittest import TestCase - - -class TestAgentMessage(TestCase): - def test_data_is_blank(self): - try: - must_not_be_none({}) - except Exception: - self.fail("must_not_be_none() raised Exception unexpectedly") - - def test_data_is_not_blank(self): - with self.assertRaises(ValidationError) as context: - must_not_be_none(None) - assert str(context.exception) == "Data not provided" diff --git a/agent/indy_catalyst_agent/messaging/tests/test_wire_message.py b/agent/indy_catalyst_agent/messaging/tests/test_wire_message.py deleted file mode 100644 index 4dc4d196cd..0000000000 --- a/agent/indy_catalyst_agent/messaging/tests/test_wire_message.py +++ /dev/null @@ -1,17 +0,0 @@ -from ..wire_message import WireMessage - -from unittest import TestCase - - -class TestWireMessage(TestCase): - - to = "to" - _from = "from" - msg = "msg" - - def test_init(self): - """ """ - wire_message = WireMessage(self._from, self.to, self.msg) - assert wire_message._from == self._from - assert wire_message.to == self.to - assert wire_message.msg == self.msg diff --git a/agent/indy_catalyst_agent/messaging/validators.py b/agent/indy_catalyst_agent/messaging/validators.py deleted file mode 100644 index f1cf66a8af..0000000000 --- a/agent/indy_catalyst_agent/messaging/validators.py +++ /dev/null @@ -1,11 +0,0 @@ -from marshmallow import ValidationError - - -def must_not_be_none(data): - """ - - :param data: - - """ - if data is None: - raise ValidationError("Data not provided") diff --git a/agent/indy_catalyst_agent/messaging/wire_message.py b/agent/indy_catalyst_agent/messaging/wire_message.py deleted file mode 100644 index e6f805af61..0000000000 --- a/agent/indy_catalyst_agent/messaging/wire_message.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Represents a wire message. -""" - -from marshmallow import Schema, fields - - -class WireMessage: - """Class representing a wire message.""" - def __init__(self, _from: str, to: str, msg: str): - self._from = _from - self.to = to - self.msg = msg - - -class WireMessageSchema(Schema): - """Wire message schema.""" - # Avoid clobbering builtin property - _from = fields.Str(data_key="from") - to = fields.Str() - msg = fields.Str() diff --git a/agent/indy_catalyst_agent/wallet/__init__.py b/agent/indy_catalyst_agent/wallet/__init__.py index 56df67cd1a..25d8e7d0d1 100644 --- a/agent/indy_catalyst_agent/wallet/__init__.py +++ b/agent/indy_catalyst_agent/wallet/__init__.py @@ -1,6 +1 @@ -""" -Abstract and Indy wallet handling -""" - -# from .base import BaseWallet, DIDInfo, PairwiseInfo -# from .error import WalletError, WalletDuplicateError, WalletNotFoundError +"""Abstract and Indy wallet handling.""" diff --git a/agent/setup.cfg b/agent/setup.cfg index 1943ad95bc..3361595974 100644 --- a/agent/setup.cfg +++ b/agent/setup.cfg @@ -6,4 +6,5 @@ addopts = --cov-config .coveragerc --cov=indy_catalyst_agent --cov-report term - # https://github.com/ambv/black#line-length max-line-length = 88 exclude = - */tests/** \ No newline at end of file + */tests/** +ignore = D202, W503 \ No newline at end of file From 5643e60470aaec7e576a8b5a55d457114f7b61bb Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Tue, 19 Feb 2019 10:29:24 -0700 Subject: [PATCH 3/8] More docs Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/models/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/agent/indy_catalyst_agent/models/base.py b/agent/indy_catalyst_agent/models/base.py index e1c4a38f9d..bdc7fd738d 100644 --- a/agent/indy_catalyst_agent/models/base.py +++ b/agent/indy_catalyst_agent/models/base.py @@ -1,6 +1,4 @@ -""" -Base classes for Models and Schemas -""" +"""Base classes for Models and Schemas.""" from abc import ABC import json @@ -12,6 +10,9 @@ def resolve_class(the_cls, relative_cls: type = None): + """ + Resolve a class + """ resolved = None if isinstance(the_cls, type): resolved = the_cls From b2985896c79e1d315b2ebc623b06e5ac5fe82eae Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Tue, 19 Feb 2019 20:37:44 -0700 Subject: [PATCH 4/8] More docs Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/models/base.py | 142 +++++++++++++++++- .../models/connection_detail.py | 57 ++++++- .../models/connection_target.py | 22 ++- .../models/field_signature.py | 25 ++- 4 files changed, 228 insertions(+), 18 deletions(-) diff --git a/agent/indy_catalyst_agent/models/base.py b/agent/indy_catalyst_agent/models/base.py index bdc7fd738d..9d002967a6 100644 --- a/agent/indy_catalyst_agent/models/base.py +++ b/agent/indy_catalyst_agent/models/base.py @@ -11,7 +11,18 @@ def resolve_class(the_cls, relative_cls: type = None): """ - Resolve a class + Resolve a class. + + Args: + the_cls: The class to resolve + relative_cls: Relative class to resolve from + + Returns: + The resolved class + + Raises: + ImportError: If the class could not be loaded + """ resolved = None if isinstance(the_cls, type): @@ -25,6 +36,17 @@ def resolve_class(the_cls, relative_cls: type = None): def resolve_meta_property(obj, prop_name: str, defval=None): + """ + Resolve a meta property. + + Args: + prop_name: The property to resolve + defval: The default value + + Returns: + The meta property + + """ cls = obj.__class__ found = defval while cls: @@ -39,10 +61,21 @@ def resolve_meta_property(obj, prop_name: str, defval=None): class BaseModel(ABC): + """Base model that provides convenience methods.""" + class Meta: + """BaseModel meta data.""" + schema_class = None def __init__(self): + """ + Initialize BaseModel. + + Raises: + TypeError: If schema_class is not set on Meta + + """ if not self.Meta.schema_class: raise TypeError( "Can't instantiate abstract class {} with no schema_class".format( @@ -52,22 +85,51 @@ def __init__(self): @classmethod def _get_schema_class(cls): + """ + Get the schema class. + + Returns: + The resolved schema class + + """ return resolve_class(cls.Meta.schema_class, cls) @property def Schema(self) -> type: - """Accessor for the model's schema class""" + """ + Accessor for the model's schema class. + + Returns: + The schema class + + """ return self._get_schema_class() @classmethod def deserialize(cls, obj): - """Convert from JSON representation to a model instance.""" + """ + Convert from JSON representation to a model instance. + + Args: + obj: The dict to load into a model instance + + Returns: + A model instance for this data + + """ schema = cls._get_schema_class()() return schema.loads(obj) if isinstance(obj, str) else schema.load(obj) def serialize(self, as_string=False) -> dict: """ - Create a JSON-compatible dict representation of the model instance + Create a JSON-compatible dict representation of the model instance. + + Args: + as_string: Return a string of JSON instead of a dict + + Returns: + A dict representation of this model, or a JSON string if as_string is True + """ schema = self.Schema() return schema.dumps(self) if as_string else schema.dump(self) @@ -75,29 +137,58 @@ def serialize(self, as_string=False) -> dict: @classmethod def from_json(cls, json_repr: Union[str, bytes]): """ - Parse a JSON string into a model instance + Parse a JSON string into a model instance. + + Args: + json_repr: JSON string + + Returns: + A model instance representation of this JSON + """ parsed = json.loads(json_repr) return cls.deserialize(parsed) def to_json(self) -> str: """ - Create a JSON representation of the model instance + Create a JSON representation of the model instance. + + Returns: + A JSON representation of this message + """ return json.dumps(self.serialize()) def __repr__(self) -> str: + """ + Return a human readable representation of this class. + + Returns: + A human readable string for this class + + """ items = ("{}={}".format(k, repr(v)) for k, v in self.__dict__.items()) return "<{}({})>".format(self.__class__.__name__, ", ".join(items)) class BaseModelSchema(Schema): + """BaseModel schema.""" + class Meta: + """BaseModelSchema metadata.""" + model_class = None skip_values = [None] ordered = True def __init__(self, *args, **kwargs): + """ + Initialize BaseModelSchema. + + Raises: + TypeError: If model_class is not set on Meta + + """ super(BaseModelSchema, self).__init__(*args, **kwargs) if not self.Meta.model_class: raise TypeError( @@ -108,15 +199,38 @@ def __init__(self, *args, **kwargs): @classmethod def _get_model_class(cls): + """ + Get the model class. + + Returns: + The model class + + """ return resolve_class(cls.Meta.model_class, cls) @property def Model(self) -> type: - """Accessor for the schema's model class""" + """ + Accessor for the schema's model class. + + Returns: + The model class + + """ return self._get_model_class() @pre_load def skip_dump_only(self, data): + """ + Skip fields that are only expected during serialization. + + Args: + data: The incoming data to clean + + Returns: + The modified data + + """ # not sure why this is necessary, seems like a bug to_remove = { field_obj.data_key or field_name @@ -130,9 +244,23 @@ def skip_dump_only(self, data): @post_load def make_model(self, data: dict): + """ + Return model instance after loading. + + Returns: + A model instance + + """ return self.Model(**data) @post_dump def remove_skipped_values(self, data): + """ + Remove values that are are marked to skip. + + Returns: + Returns this modified data + + """ skip_vals = resolve_meta_property(self, "skip_values", []) return {key: value for key, value in data.items() if value not in skip_vals} diff --git a/agent/indy_catalyst_agent/models/connection_detail.py b/agent/indy_catalyst_agent/models/connection_detail.py index 0c11a3f469..a2a27217b9 100644 --- a/agent/indy_catalyst_agent/models/connection_detail.py +++ b/agent/indy_catalyst_agent/models/connection_detail.py @@ -1,6 +1,4 @@ -""" -An object for containing the connection request/response DID information -""" +"""An object for containing the connection request/response DID information.""" from marshmallow import fields @@ -9,12 +7,32 @@ class DIDDocWrapper(fields.Field): - """Field that loads and serializes DIDDoc""" + """Field that loads and serializes DIDDoc.""" def _serialize(self, value, attr, obj, **kwargs): + """ + Serialize the DIDDoc. + + Args: + value: The value to serialize + + Returns: + The serialized DIDDoc + + """ return value.serialize() def _deserialize(self, value, attr, data, **kwargs): + """ + Deserialize a value into a DIDDoc. + + Args: + value: The value to deserialize + + Returns: + The deserialized value + + """ # quick fix for missing optional values if "authentication" not in value: value["authentication"] = [] @@ -24,28 +42,55 @@ def _deserialize(self, value, attr, data, **kwargs): class ConnectionDetail(BaseModel): + """Class representing the details of a connection.""" + class Meta: + """ConnectionDetail metadata.""" schema_class = "ConnectionDetailSchema" def __init__(self, *, did: str = None, did_doc: DIDDoc = None, **kwargs): + """ + Initialize a ConnectionDetail instance. + + Args: + did: DID for the connection detail + did_doc: DIDDoc for connection detail + + """ super(ConnectionDetail, self).__init__(**kwargs) self._did = did self._did_doc = did_doc @property def did(self) -> str: - """Accessor for the connection DID""" + """ + Accessor for the connection DID. + + Returns: + The DID for this connection + + """ return self._did @property def did_doc(self) -> DIDDoc: - """Accessor for the connection DID Document""" + """ + Accessor for the connection DID Document. + + Returns: + The DIDDoc for this connection + + """ return self._did_doc class ConnectionDetailSchema(BaseModelSchema): + """ConnectionDetail schema.""" + class Meta: + """ConnectionDetailSchema metadata.""" + model_class = "ConnectionDetail" did = fields.Str(data_key="DID") diff --git a/agent/indy_catalyst_agent/models/connection_target.py b/agent/indy_catalyst_agent/models/connection_target.py index 72983a2ad0..3c375ce8af 100644 --- a/agent/indy_catalyst_agent/models/connection_target.py +++ b/agent/indy_catalyst_agent/models/connection_target.py @@ -1,6 +1,4 @@ -""" -Record used to handle routing of messages to another agent -""" +"""Record used to handle routing of messages to another agent.""" from typing import Sequence @@ -10,7 +8,11 @@ class ConnectionTarget(BaseModel): + """Record used to handle routing of messages to another agent.""" + class Meta: + """ConnectionTarget metadata.""" + schema_class = "ConnectionTargetSchema" def __init__( @@ -23,6 +25,16 @@ def __init__( routing_keys: Sequence[str] = None, sender_key: str = None, ): + """ + Initialize a ConnectionTarget instance. + + Args: + did: A did for the connection + endpoint: An endpoint for the connection + label: A label for the connection + recipient_key: A list of recipient keys + routing_keys: A list of routing keys + """ self.did = did self.endpoint = endpoint self.label = label @@ -32,7 +44,11 @@ def __init__( class ConnectionTargetSchema(BaseModelSchema): + """ConnectionTarget schema.""" + class Meta: + """ConnectionTargetSchema metadata.""" + model_class = ConnectionTarget did = fields.Str(required=False) diff --git a/agent/indy_catalyst_agent/models/field_signature.py b/agent/indy_catalyst_agent/models/field_signature.py index 0a523d6857..c6579f553c 100644 --- a/agent/indy_catalyst_agent/models/field_signature.py +++ b/agent/indy_catalyst_agent/models/field_signature.py @@ -15,9 +15,10 @@ class FieldSignature(BaseModel): - """Class representing a field value signed by a known verkey""" + """Class representing a field value signed by a known verkey.""" class Meta: + """FieldSignature metadata.""" schema_class = "FieldSignatureSchema" @@ -33,6 +34,15 @@ def __init__( sig_data: str = None, signer: str = None, ): + """ + Initialize a FieldSignature instance. + + Args: + signature_type: Type of signature + signature: The signature + sig_data: Signature data + signer: The verkey of the signer + """ self.signature_type = signature_type self.signature = signature self.sig_data = sig_data @@ -43,8 +53,19 @@ async def create( cls, value, signer: str, wallet: BaseWallet, timestamp=None ) -> "FieldSignature": """ + Create a Signature. + Sign a field value and return a newly constructed `FieldSignature` representing - the resulting signature + the resulting signature. + + Args: + value: + signer: + wallet: The wallet to use for the signature + + Returns: + The created `FieldSignature` object + """ if not timestamp: timestamp = time.time() From b46184a6dc4a90e09f33728f79d82b226a95fa18 Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Wed, 20 Feb 2019 15:20:32 -0700 Subject: [PATCH 5/8] Update docs Signed-off-by: Nicholas Rempel --- .../models/field_signature.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/agent/indy_catalyst_agent/models/field_signature.py b/agent/indy_catalyst_agent/models/field_signature.py index c6579f553c..e864fa4099 100644 --- a/agent/indy_catalyst_agent/models/field_signature.py +++ b/agent/indy_catalyst_agent/models/field_signature.py @@ -59,10 +59,10 @@ async def create( the resulting signature. Args: - value: - signer: + value: Value to sign + signer: Verkey of the signing party wallet: The wallet to use for the signature - + Returns: The created `FieldSignature` object @@ -84,7 +84,10 @@ async def create( def decode(self) -> (object, int): """ - Decode the signature to its timestamp and value + Decode the signature to its timestamp and value. + + Returns: + A tuple of (decoded message, timestamp) """ msg_bin = b64_to_bytes(self.sig_data, urlsafe=True) timestamp, = struct.unpack_from("!Q", msg_bin, 0) @@ -92,7 +95,13 @@ def decode(self) -> (object, int): async def verify(self, wallet: BaseWallet) -> bool: """ - Verify the signature against the signer's public key + Verify the signature against the signer's public key. + + Args: + wallet: Wallet to use to verify signature + + Returns: + True if verification succeeds else False """ if self.signature_type != self.TYPE_ED25519SHA512: return False From bba2ef5c824f872f3ff217aa2635e6fbc339b66f Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Mon, 25 Feb 2019 11:10:54 -0800 Subject: [PATCH 6/8] More docs Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/classloader.py | 40 ++- agent/indy_catalyst_agent/conductor.py | 33 ++- agent/indy_catalyst_agent/connection.py | 172 ++++++++++-- agent/indy_catalyst_agent/defaults.py | 2 + agent/indy_catalyst_agent/dispatcher.py | 58 +++- agent/indy_catalyst_agent/error.py | 6 +- .../basicmessage/messages/basicmessage.py | 9 +- .../models/field_signature.py | 6 + .../models/thread_decorator.py | 65 ++++- agent/indy_catalyst_agent/version.py | 4 +- agent/indy_catalyst_agent/wallet/base.py | 189 ++++++++++--- agent/indy_catalyst_agent/wallet/basic.py | 253 +++++++++++++++--- agent/indy_catalyst_agent/wallet/crypto.py | 197 ++++++++++++-- agent/indy_catalyst_agent/wallet/error.py | 10 +- agent/indy_catalyst_agent/wallet/indy.py | 183 +++++++++++-- agent/indy_catalyst_agent/wallet/util.py | 20 +- agent/requirements.txt | 2 +- 17 files changed, 1055 insertions(+), 194 deletions(-) diff --git a/agent/indy_catalyst_agent/classloader.py b/agent/indy_catalyst_agent/classloader.py index ce66df1562..6037ec6358 100644 --- a/agent/indy_catalyst_agent/classloader.py +++ b/agent/indy_catalyst_agent/classloader.py @@ -1,3 +1,5 @@ +"""The classloader provides utilties to dynamically load classes and modules.""" + import inspect import logging @@ -22,6 +24,13 @@ class ClassLoader: """Class used to load classes from modules dynamically.""" def __init__(self, base_path, super_class): + """ + Initialize a ClassLoader instance. + + Args: + base_path: The base dotted path to look for a relative import + super_class: Look for a class that inherits from this class + """ self.logger = logging.getLogger(__name__) self.base_path = base_path self.super_class = super_class @@ -30,9 +39,18 @@ def load(self, module_path, load_relative=False): """ Load module by module path. - :param module_path: Dotted path to module - :param load_relative: (Default value = False) Should the method check in the - configured base path for relative import + Args: + module_path: Dotted path to module + load_relative: Should the method check in the + configured base path for relative import + + Return: + The loaded class + + Raises: + ModuleLoadError: If there is an error loading the class + ClassNotFoundError: If there is no class to load at specified path + """ # We can try to load the module relative to a given base path if load_relative: @@ -67,10 +85,20 @@ def load(self, module_path, load_relative=False): @classmethod def load_class(cls, class_name: str, default_module: str = None): - """Resolve a complete class path (ie. typing.Dict) to the class itself + """ + Resolve a complete class path (ie. typing.Dict) to the class itself. + + Args: + class_name: Class name + default_module: (Default value = None) + + Returns: + The resolved class + + Raises: + ClassNotFoundError: If the class could not be resolved at path + ModuleLoadError: If there was an error loading the module - :param class_name: str: Class name - :param default_module: str: (Default value = None) """ if "." in class_name: # import module and find class diff --git a/agent/indy_catalyst_agent/conductor.py b/agent/indy_catalyst_agent/conductor.py index 3a41038e35..3f3f28c108 100644 --- a/agent/indy_catalyst_agent/conductor.py +++ b/agent/indy_catalyst_agent/conductor.py @@ -1,7 +1,11 @@ """ +The Conductor. + The conductor is responsible for coordinating messages that are received over the network, communicating with the ledger, passing messages to handlers, -and storing data in the wallet. +instantiating concrete implementations of required modules and storing data in the +wallet. + """ import logging @@ -31,6 +35,8 @@ class ConductorError(BaseError): class Conductor: """ + Conductor class. + Class responsible for initalizing concrete implementations of our require interfaces and routing inbound and outbound message data. """ @@ -51,6 +57,16 @@ def __init__( message_factory: MessageFactory, settings: dict, ) -> None: + """ + Initialize an instance of Conductor. + + Args: + transport_configs: Configuration for inbound transport + outbound_transports: Configuration for outbound transport + message_factory: Message factory for discovering and deserializing messages + settings: Dictionary of various settings + + """ self.context = None self.connection_mgr = None self.logger = logging.getLogger(__name__) @@ -155,7 +171,13 @@ async def inbound_message_router( reply: Coroutine = None, ): """ - Routes inbound messages. + Route inbound messages. + + Args: + message_body: Body of the incoming message + transport_type: Type of transport this message came from + reply: Function to reply to this message + """ context = await self.connection_mgr.expand_message(message_body, transport_type) result = await self.dispatcher.dispatch( @@ -168,5 +190,12 @@ async def inbound_message_router( async def outbound_message_router( self, message: AgentMessage, target: ConnectionTarget ) -> None: + """ + Route an outbound message. + + Args: + message: An agent message to be sent + target: Target to send message to + """ payload = await self.connection_mgr.compact_message(message, target) await self.outbound_transport_manager.send_message(payload, target.endpoint) diff --git a/agent/indy_catalyst_agent/connection.py b/agent/indy_catalyst_agent/connection.py index 30a3c6bf90..d2041e519b 100644 --- a/agent/indy_catalyst_agent/connection.py +++ b/agent/indy_catalyst_agent/connection.py @@ -1,6 +1,4 @@ -""" -Connection management -""" +"""Classes to manage connections.""" import aiohttp import json @@ -33,14 +31,21 @@ class ConnectionManagerError(BaseError): class ConnectionRecord: + """Assembles connection state and target information.""" + STATE_INVITED = "invited" STATE_REQUESTED = "requested" STATE_RESPONDED = "responded" STATE_COMPLETE = "complete" - """Assembles connection state and target information""" - def __init__(self, state: str, target: ConnectionTarget): + """ + Initialize a ConnectionRecord. + + Args: + state: The current state of this connection + target: The target for this connection + """ self.state = state self.target = target @@ -49,12 +54,24 @@ class ConnectionManager: """Class for managing connections.""" def __init__(self, context: RequestContext): + """ + Initialize a ConnectionManager. + + Args: + context: The context for this connection + """ self._context = context self._logger = logging.getLogger(__name__) @property def context(self) -> RequestContext: - """Accessor for the current request context""" + """ + Accessor for the current request context. + + Returns: + The request context for this connection + + """ return self._context async def create_invitation( @@ -62,18 +79,22 @@ async def create_invitation( ) -> ConnectionInvitation: """ Generate new connection invitation. + This interaction represents an out-of-band communication channel. In the future and in practice, these sort of invitations will be received over any number of channels such as SMS, Email, QR Code, NFC, etc. Structure of an invite message: + ```json { "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation", "label": "Alice", "did": "did:sov:QmWbsNYhMrjHiqZDTUTEJs" } + ``` Or, in the case of a peer DID: + ```json { "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation", "label": "Alice", @@ -81,7 +102,19 @@ async def create_invitation( "recipientKeys": ["8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K"], "serviceEndpoint": "https://example.com/endpoint" } + ``` + Currently, only peer DID is supported. + + Args: + label: Label for this connection + my_endpoint: Endpoint where other party can reach me + seed: Seed for key + metadata: Metadata for key + + Returns: + A new ConnectionInvitation + """ self._logger.debug("Creating invitation") @@ -99,7 +132,11 @@ async def create_invitation( async def send_invitation(self, invitation: ConnectionInvitation, endpoint: str): """ - Deliver an invitation to an HTTP endpoint + Deliver an invitation to an HTTP endpoint. + + Args: + invitation: The `ConnectionInvitation` to send + endpoint: Endpoint to send this invitation to """ self._logger.debug("Sending invitation to %s", endpoint) invite_json = invitation.to_json() @@ -111,7 +148,16 @@ async def store_invitation( self, invitation: ConnectionInvitation, received: bool, tags: dict = None ) -> str: """ - Save an invitation for acceptance/rejection and later processing + Save an invitation for acceptance/rejection and later processing. + + Args: + invitation: A `ConnectionInvitation` to store + received: Has the invitation been received + tags: A `dict` of tags for the storage record + + Returns: + The invitation id + """ # may want to generate another unique ID, or use the message ID # instead of the key @@ -137,6 +183,14 @@ async def find_invitation( ) -> Tuple[ConnectionInvitation, dict]: """ Locate a previously-received invitation. + + Args: + invitation_id: Id for the storeg invitation + received: Was the invitation received + + Returns: + A tuple of the stored `ConnectionInvitation` and it's tags + """ self._logger.debug( "Looking up %s invitation: %s", @@ -152,7 +206,11 @@ async def find_invitation( async def remove_invitation(self, invitation_id: str): """ - Remove a previously-stored invitation + Remove a previously-stored invitation. + + Args: + The invitation id + """ # raises StorageNotFoundError if not found await self.context.storage.delete_record("invitation", invitation_id) @@ -164,7 +222,16 @@ async def accept_invitation( my_endpoint: str = None, ) -> Tuple[ConnectionRequest, ConnectionTarget]: """ - Create a new connection request for a previously-received invitation + Create a new connection request for a previously-received invitation. + + Args: + invitation: The `ConnectionInvitation` to accept + my_label: My label + my_endpoint: My endpoint + + Returns: + A tuple of created `ConnectionRequest` and `ConnectionTarget` + """ their_label = invitation.label @@ -214,7 +281,14 @@ async def accept_invitation( async def find_request(self, request_id: str) -> ConnectionRequest: """ - Locate a previously saved connection request + Locate a previously saved connection request. + + Args: + request_id: The id of the connection request + + Returns: + The found `ConnectionRequest` + """ # raises exception if not found result = await self.context.storage.get_record("connection_request", request_id) @@ -223,7 +297,11 @@ async def find_request(self, request_id: str) -> ConnectionRequest: async def remove_request(self, request_id: str): """ - Remove a previously-stored connection request + Remove a previously-stored connection request. + + Args: + request_id: The id of the connection request + """ # raises exception if not found await self.context.storage.delete_record("connection_request", request_id) @@ -233,6 +311,14 @@ async def accept_request( ) -> Tuple[ConnectionResponse, ConnectionTarget]: """ Create a connection response for a received connection request. + + Args: + request: The `ConnectionRequest` to accept + my_endpoint: The endpoint I can be reached at + + Returns: + A tuple of the resulting `ConnectionResponse` and `ConnectionTarget` + """ invitation = None @@ -303,8 +389,22 @@ async def accept_request( async def accept_response(self, response: ConnectionResponse) -> ConnectionTarget: """ + Accept a connection response. + Process a ConnectionResponse message by looking up - the connection request and setting up the pairwise connection + the connection request and setting up the pairwise connection. + + Args: + response: The `ConnectionResponse` to accept + + Returns: + A `ConnectionTarget` for the new connection + + Raises: + ConnectionManagerError: If there is no DID associated with the + connection response + ConnectionManagerError: If the DID is not associated with a connection + """ if response._thread: request_id = response._thread.thid @@ -372,8 +472,19 @@ async def accept_response(self, response: ConnectionResponse) -> ConnectionTarge async def find_connection( self, my_verkey: str, their_verkey: str, auto_complete=False - ) -> dict: - """Look up existing connection information for a sender verkey""" + ) -> ConnectionRecord: + """ + Look up existing connection information for a sender verkey. + + Args: + my_verkey: My verkey + their_verkey: Their verkey + auto_complete: Should this connection automatically be completed + + Returns: + The found `ConnectionRecord` + + """ try: pairwise = await self.context.wallet.get_pairwise_for_verkey(their_verkey) except WalletNotFoundError: @@ -446,7 +557,19 @@ async def expand_message( self, message_body: Union[str, bytes], transport_type: str ) -> RequestContext: """ - Deserialize an incoming message and further populate the request context + Deserialize an incoming message and further populate the request context. + + message_body: The body of the message + transport_type: The transport the message was received on + + Returns: + The `RequestContext` of the expanded message + + Raises: + MessageParseError: If there is no message factory defined + MessageParseError: If there is no wallet defined + MessageParseError: If the JSON parsing failed + """ if not self.context.message_factory: raise MessageParseError("Message factory not defined") @@ -460,10 +583,9 @@ async def expand_message( if isinstance(message_body, bytes): try: - message_json, from_verkey, to_verkey = \ - await self.context.wallet.unpack_message( - message_body - ) + message_json, from_verkey, to_verkey = await self.context.wallet.unpack_message( + message_body + ) except WalletError: self._logger.debug("Message unpack failed, trying JSON") @@ -509,7 +631,15 @@ async def compact_message( self, message: AgentMessage, target: ConnectionTarget ) -> Union[str, bytes]: """ - Serialize an outgoing message for transport + Serialize an outgoing message for transport. + + Args: + message: The `AgentMessage` to compact + target: The `ConnectionTarget` you are compacting for + + Returns: + The compacted message + """ message_dict = message.serialize() message_json = json.dumps(message_dict) diff --git a/agent/indy_catalyst_agent/defaults.py b/agent/indy_catalyst_agent/defaults.py index f030c7582e..24e9aec688 100644 --- a/agent/indy_catalyst_agent/defaults.py +++ b/agent/indy_catalyst_agent/defaults.py @@ -1,3 +1,5 @@ +"""Sane defaults for known message definitions.""" + from .messaging.message_factory import MessageFactory from .messaging.message_types import MessageTypes diff --git a/agent/indy_catalyst_agent/dispatcher.py b/agent/indy_catalyst_agent/dispatcher.py index 29b74e2bdb..fb520b9f7b 100644 --- a/agent/indy_catalyst_agent/dispatcher.py +++ b/agent/indy_catalyst_agent/dispatcher.py @@ -1,4 +1,6 @@ """ +The Dispatcher. + The dispatcher is responsible for coordinating data flow between handlers, providing lifecycle hook callbacks storing state for message threads, etc. """ @@ -15,11 +17,14 @@ class Dispatcher: """ + Dispatcher class. + Class responsible for dispatching messages to message handlers and responding to other agents. """ def __init__(self): + """Initialize an instance of Dispatcher.""" self.logger = logging.getLogger(__name__) async def dispatch( @@ -30,6 +35,15 @@ async def dispatch( ): """ Configure responder and dispatch message context to message handler. + + Args: + context: The `RequestContext` to be handled + send: Function to send outbound messages + transport_reply: Function to reply on the incoming channel + + Returns: + The response from the handler + """ responder = self.make_responder(send, context, transport_reply) @@ -42,9 +56,18 @@ async def dispatch( def make_responder( self, send: Coroutine, context: RequestContext, reply: Union[Coroutine, None] - ): + ) -> "DispatcherResponder": """ Build a responder object. + + Args: + send: Function to send outbound messages + context: The `RequestContext` to be handled + reply: Function to reply on the incoming channel + + Returns: + The created `DispatcherResponder` + """ responder = DispatcherResponder(send, context.wallet, reply=reply) # responder.add_target(ConnectionTarget(endpoint="wss://0bc6628c.ngrok.io")) @@ -58,11 +81,21 @@ def make_responder( class DispatcherResponder(BaseResponder): - """Handle outgoing messages from message handlers""" + """Handle outgoing messages from message handlers.""" def __init__( self, send: Coroutine, wallet: BaseWallet, *targets, reply: Coroutine = None ): + """ + Initialize an instance of `DispatcherResponder`. + + Args: + send: Function to send outbound message + wallet: Wallet instance to use + targets: List of `ConnectionTarget`s to send to + reply: Function to reply on incoming channel + + """ self._targets = list(targets) self._send = send self._reply = reply @@ -72,11 +105,22 @@ def add_target(self, target: ConnectionTarget): """ Add target. - :param target: ConnectionTarget: Connection target + Args: + target: ConnectionTarget to add """ self._targets.append(target) async def send_reply(self, message: AgentMessage): + """ + Send a reply to an incoming message. + + Args: + message: `AgentMessage` to reply with + + Raises: + ResponderError: If there is no active connection + + """ if self._reply: # 'reply' is a temporary solution to support responses to websocket requests # a better solution would likely use a queue to deliver the replies @@ -88,7 +132,15 @@ async def send_reply(self, message: AgentMessage): await self.send_outbound(message, target) async def send_outbound(self, message: AgentMessage, target: ConnectionTarget): + """ + Send outbound message. + + Args: + message: `AgentMessage` to send + target: `ConnectionTarget` to send to + """ await self._send(message, target) async def send_admin_message(self, message: AgentMessage): + """Todo.""" pass diff --git a/agent/indy_catalyst_agent/error.py b/agent/indy_catalyst_agent/error.py index 98cd4b51b3..82c476cec5 100644 --- a/agent/indy_catalyst_agent/error.py +++ b/agent/indy_catalyst_agent/error.py @@ -1,7 +1,5 @@ -""" -Common exception classes -""" +"""Common exception classes.""" class BaseError(Exception): - """Generic exception class which other exceptions should inherit from""" + """Generic exception class which other exceptions should inherit from.""" diff --git a/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py b/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py index 7ef255c938..821033a716 100644 --- a/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py +++ b/agent/indy_catalyst_agent/messaging/basicmessage/messages/basicmessage.py @@ -25,7 +25,12 @@ class Meta: schema_class = "BasicMessageSchema" def __init__( - self, *, sent_time: Union[str, datetime] = None, content: str = None, **kwargs + self, + *, + sent_time: Union[str, datetime] = None, + content: str = None, + localization: str = None, + **kwargs ): """ Initialize basic message object. @@ -41,6 +46,7 @@ def __init__( sent_time = sent_time.replace(tzinfo=timezone.utc).isoformat(" ") self.sent_time = sent_time self.content = content + self.localization = localization class BasicMessageSchema(AgentMessageSchema): @@ -51,5 +57,6 @@ class Meta: model_class = BasicMessage + localization = fields.Str(data_key="l10n", required=False) sent_time = fields.Str(required=False) content = fields.Str(required=True) diff --git a/agent/indy_catalyst_agent/models/field_signature.py b/agent/indy_catalyst_agent/models/field_signature.py index e864fa4099..06ea5b3cbc 100644 --- a/agent/indy_catalyst_agent/models/field_signature.py +++ b/agent/indy_catalyst_agent/models/field_signature.py @@ -102,6 +102,7 @@ async def verify(self, wallet: BaseWallet) -> bool: Returns: True if verification succeeds else False + """ if self.signature_type != self.TYPE_ED25519SHA512: return False @@ -110,6 +111,7 @@ async def verify(self, wallet: BaseWallet) -> bool: return await wallet.verify_message(msg_bin, sig_bin, self.signer) def __str__(self): + """Get a string representation of this class.""" return ( f"{self.__class__.__name__}" + f"(signature_type='{self.signature_type,}', " @@ -119,7 +121,11 @@ def __str__(self): class FieldSignatureSchema(BaseModelSchema): + """FieldSignature schema.""" + class Meta: + """FieldSignatureSchema metadata.""" + model_class = FieldSignature signature_type = fields.Str(data_key="@type", required=True) diff --git a/agent/indy_catalyst_agent/models/thread_decorator.py b/agent/indy_catalyst_agent/models/thread_decorator.py index d60fec7437..f46f7812fa 100644 --- a/agent/indy_catalyst_agent/models/thread_decorator.py +++ b/agent/indy_catalyst_agent/models/thread_decorator.py @@ -1,4 +1,6 @@ """ +A message decorator for threads. + A thread decorator identifies a message that may require additional context from previous messages. """ @@ -14,6 +16,8 @@ class ThreadDecorator(BaseModel): """Class representing thread decorator.""" class Meta: + """ThreadDecorator metadata.""" + schema_class = "ThreadDecoratorSchema" def __init__( @@ -24,6 +28,24 @@ def __init__( sender_order: int = None, received_orders: Mapping = None, ): + """ + Initialize a ThreadDecorator instance. + + Args: + thid: The ID of the message that serves as the + thread start + pthid: An optional parent thid. Used when branching + or nesting a new interaction off of an existing one. + sender_order:A number that tells where this message + fits in the sequence of all messages that the + current sender has contributed to this thread + received_orders: Reports the highest sender_order value + that the sender has seen from other sender(s) on the + thread. (This value is often missing if it is the first + message in an interaction, but should be used otherwise, + as it provides an implicit ACK.) + + """ super(ThreadDecorator, self).__init__() self._thid = thid self._pthid = pthid @@ -32,44 +54,67 @@ def __init__( @property def thid(self): - """Accessor for thread identifier""" + """ + Accessor for thread identifier. + + Returns: + This thread's `thid` + + """ return self._thid @property def pthid(self): - """Accessor for parent thread identifier""" + """ + Accessor for parent thread identifier. + + Returns: + This thread's `pthid` + + """ return self._pthid @pthid.setter def pthid(self, val: str): - """Setter for parent thread identifier - - :param val: str: new pthid + """ + Setter for parent thread identifier. + Args: + val: The new pthid """ self._pthid = val @property def received_orders(self) -> dict: """ - Reports the highest sender_order value that the sender has seen from other - sender(s) on the thread + Get received orders. + + Returns: + The highest sender_order value that the sender has seen from other + sender(s) on the thread. + """ return self._received_orders @property def sender_order(self) -> int: """ - A number that tells where this message fits in the sequence of all - messages that the current sender has contributed to this thread + Get sender order. + + Returns: + A number that tells where this message fits in the sequence of all + messages that the current sender has contributed to this thread + """ return self._sender_order class ThreadDecoratorSchema(BaseModelSchema): - """Thread decorator schema used in serialization/deserialization""" + """Thread decorator schema used in serialization/deserialization.""" class Meta: + """ThreadDecoratorSchema metadata.""" + model_class = ThreadDecorator thid = fields.Str() diff --git a/agent/indy_catalyst_agent/version.py b/agent/indy_catalyst_agent/version.py index 555c3f0957..198915926e 100644 --- a/agent/indy_catalyst_agent/version.py +++ b/agent/indy_catalyst_agent/version.py @@ -1,5 +1,3 @@ -""" -Library version information -""" +"""Library version information.""" __version__ = "0.0.2" diff --git a/agent/indy_catalyst_agent/wallet/base.py b/agent/indy_catalyst_agent/wallet/base.py index e5be02bcbf..ca8ae4a7a3 100644 --- a/agent/indy_catalyst_agent/wallet/base.py +++ b/agent/indy_catalyst_agent/wallet/base.py @@ -1,6 +1,4 @@ -""" -Wallet base class -""" +"""Wallet base class.""" from abc import ABC, abstractmethod from collections import namedtuple @@ -16,77 +14,86 @@ class BaseWallet(ABC): - """Abstract wallet interface""" + """Abstract wallet interface.""" + # TODO: break config out into params? def __init__(self, config: dict): """ - config: {name, key, seed, did, auto-create, auto-remove} + Initialize a `BaseWallet` instance. + + Args: + config: {name, key, seed, did, auto-create, auto-remove} + """ @property def handle(self): - """Get internal wallet reference""" + """ + Get internal wallet reference. + + Returns: + Defaults to None + + """ return None @property def opened(self) -> bool: - """Check whether wallet is currently open""" + """ + Check whether wallet is currently open. + + Returns: + Defaults to False + + """ return False @abstractmethod async def open(self): - """ - Open wallet, removing and/or creating it if so configured - """ + """Open wallet, removing and/or creating it if so configured.""" @abstractmethod async def close(self): - """ - Close previously-opened wallet, removing it if so configured - """ + """Close previously-opened wallet, removing it if so configured.""" @abstractmethod async def create_signing_key( self, seed: str = None, metadata: dict = None ) -> KeyInfo: """ - Create a new public/private signing keypair + Create a new public/private signing keypair. Args: seed: Optional seed allowing deterministic key creation metadata: Optional metadata to store with the keypair - Returns: a `KeyInfo` representing the new record + Returns: + A `KeyInfo` representing the new record - Raises: - WalletDuplicateError: If the resulting verkey already exists in the wallet """ @abstractmethod async def get_signing_key(self, verkey: str) -> KeyInfo: """ - Fetch info for a signing keypair + Fetch info for a signing keypair. Args: verkey: The verification key of the keypair - Returns: a `KeyInfo` representing the keypair + Returns: + A `KeyInfo` representing the keypair - Raises: - WalletNotFoundError: if no keypair is associated with the verification key """ @abstractmethod async def replace_signing_key_metadata(self, verkey: str, metadata: dict): """ - Replace the metadata associated with a signing keypair + Replace the metadata associated with a signing keypair. Args: verkey: The verification key of the keypair metadata: The new metadata to store - Raises: - WalletNotFoundError: if no keypair is associated with the verification key """ @abstractmethod @@ -94,31 +101,63 @@ async def create_local_did( self, seed: str = None, did: str = None, metadata: dict = None ) -> DIDInfo: """ - Create and store a new local DID + Create and store a new local DID. + + Args: + seed: Optional seed to use for did + did: The DID to use + metadata: Metadata to store with DID + + Returns: + The created `DIDInfo` + """ @abstractmethod async def get_local_dids(self) -> Sequence[DIDInfo]: """ - Get list of defined local DIDs + Get list of defined local DIDs. + + Returns: + A list of `DIDInfo` instances + """ @abstractmethod async def get_local_did(self, did: str) -> DIDInfo: """ - Find info for a local DID + Find info for a local DID. + + Args: + did: The DID to get info for + + Returns: + A `DIDInfo` instance for the DID + """ @abstractmethod async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: """ - Resolve a local DID from a verkey + Resolve a local DID from a verkey. + + Args: + verkey: Verkey to get DID info for + + Returns: + A `DIDInfo` instance for the DID + """ @abstractmethod async def replace_local_did_metadata(self, did: str, metadata: dict): """ - Replace the metadata associated with a local DID + Replace the metadata associated with a local DID. + + Args: + did: DID to replace metadata for + metadata: The new metadata + """ @abstractmethod @@ -130,37 +169,77 @@ async def create_pairwise( metadata: dict = None, ) -> PairwiseInfo: """ - Create a new pairwise DID for a secure connection + Create a new pairwise DID for a secure connection. + + Args: + their_did: Their DID + their_verkey: Their verkey + my_did: My DID + metadata: Metadata for relationship + + Returns: + A `PairwiseInfo` instance representing the new relationship + """ @abstractmethod async def get_pairwise_list(self) -> Sequence[PairwiseInfo]: """ - Get list of defined pairwise DIDs + Get list of defined pairwise DIDs. + + Returns: + A list of `PairwiseInfo` instances for all relationships + """ @abstractmethod async def get_pairwise_for_did(self, their_did: str) -> PairwiseInfo: """ - Find info for a pairwise DID + Find info for a pairwise DID. + + Args: + their_did: The DID representing the relationship + + Returns: + A `PairwiseInfo` instance representing the relationship + """ @abstractmethod async def get_pairwise_for_verkey(self, their_verkey: str) -> PairwiseInfo: """ - Resolve a pairwise DID from a verkey + Resolve a pairwise DID from a verkey. + + Args: + their_verkey: The verkey representing the relationship + + Returns: + A `PairwiseInfo` instance representing the relationship + """ @abstractmethod async def replace_pairwise_metadata(self, their_did: str, metadata: dict): """ - Replace the metadata associated with a pairwise DID + Replace the metadata associated with a pairwise DID. + + Args: + their_did: The did representing the relationship + metadata: The new metadata + """ @abstractmethod async def sign_message(self, message: bytes, from_verkey: str) -> bytes: """ - Sign a message using the private key associated with a given verkey + Sign a message using the private key associated with a given verkey. + + Args: + message: The message to sign + from_verkey: Sign using the private key related to this verkey + + Returns: + The signature """ @abstractmethod @@ -168,7 +247,16 @@ async def verify_message( self, message: bytes, signature: bytes, from_verkey: str ) -> bool: """ - Verify a signature against the public key of the signer + Verify a signature against the public key of the signer. + + Args: + message: The message to verify + signature: The signature to verify + from_verkey: Verkey to use in verification + + Returns: + True if verified, else False + """ @abstractmethod @@ -176,7 +264,7 @@ async def encrypt_message( self, message: bytes, to_verkey: str, from_verkey: str = None ) -> bytes: """ - Apply auth_crypt or anon_crypt to a message + Apply auth_crypt or anon_crypt to a message. Args: message: The binary message content @@ -186,6 +274,7 @@ async def encrypt_message( Returns: The encrypted message content + """ @abstractmethod @@ -193,7 +282,7 @@ async def decrypt_message( self, enc_message: bytes, to_verkey: str, use_auth: bool ) -> (bytes, str): """ - Decrypt a message assembled by auth_crypt or anon_crypt + Decrypt a message assembled by auth_crypt or anon_crypt. Args: message: The encrypted message content @@ -202,7 +291,8 @@ async def decrypt_message( Returns: A tuple of the decrypted message content and sender verkey - (None for anon_crypt) + (None for anon_crypt) + """ @abstractmethod @@ -210,13 +300,29 @@ async def pack_message( self, message: str, to_verkeys: Sequence[str], from_verkey: str = None ) -> bytes: """ - Pack a message for one or more recipients + Pack a message for one or more recipients. + + Args: + message: The message to pack + to_verkeys: The verkeys to pack the message for + from_verkey: The sender verkey + + Returns: + The packed message + """ @abstractmethod async def unpack_message(self, enc_message: bytes) -> (str, str, str): """ - Unpack a message + Unpack a message. + + Args: + enc_message: The encrypted message + + Returns: + A tuple: (message, from_verkey, to_verkey) + """ # TODO: @@ -224,4 +330,5 @@ async def unpack_message(self, enc_message: bytes) -> (str, str, str): # fetch credentials by ID [or query, filter, proof request?] def __repr__(self) -> str: + """Get a human readable string.""" return "<{}(opened={})>".format(self.__class__.__name__, self.opened) diff --git a/agent/indy_catalyst_agent/wallet/basic.py b/agent/indy_catalyst_agent/wallet/basic.py index 436c7e00ed..334714f393 100644 --- a/agent/indy_catalyst_agent/wallet/basic.py +++ b/agent/indy_catalyst_agent/wallet/basic.py @@ -1,6 +1,4 @@ -""" -In-memory implementation of BaseWallet interface -""" +"""In-memory implementation of BaseWallet interface.""" from typing import Sequence @@ -23,9 +21,16 @@ class BasicWallet(BaseWallet): - """In-memory wallet implementation""" + """In-memory wallet implementation.""" def __init__(self, config: dict = None): + """ + Initialize a `BasicWallet` instance. + + Args: + config: {name, key, seed, did, auto-create, auto-remove} + + """ if not config: config = {} super(BasicWallet, self).__init__(config) @@ -36,34 +41,39 @@ def __init__(self, config: dict = None): @property def opened(self) -> bool: - """Check whether wallet is currently open""" + """ + Check whether wallet is currently open. + + Returns: + True + + """ return True async def open(self): - """ - Does not apply to in-memory wallet - """ + """Not applicable to in-memory wallet.""" pass async def close(self): - """ - Does not apply to in-memory wallet - """ + """Not applicable to in-memory wallet.""" pass async def create_signing_key( self, seed: str = None, metadata: dict = None ) -> KeyInfo: """ - Create a new public/private signing keypair + Create a new public/private signing keypair. Args: + seed: Seed to use for signing key metadata: Optional metadata to store with the keypair - Returns: a `KeyInfo` representing the new record + Returns: + A `KeyInfo` representing the new record Raises: WalletDuplicateError: If the resulting verkey already exists in the wallet + """ seed = validate_seed(seed) or random_seed() verkey, secret = create_keypair(seed) @@ -80,15 +90,17 @@ async def create_signing_key( async def get_signing_key(self, verkey: str) -> KeyInfo: """ - Fetch info for a signing keypair + Fetch info for a signing keypair. Args: verkey: The verification key of the keypair - Returns: a `KeyInfo` representing the keypair + Returns: + A `KeyInfo` representing the keypair Raises: WalletNotFoundError: if no keypair is associated with the verification key + """ if verkey not in self._keys: raise WalletNotFoundError("Key not found: {}".format(verkey)) @@ -97,7 +109,7 @@ async def get_signing_key(self, verkey: str) -> KeyInfo: async def replace_signing_key_metadata(self, verkey: str, metadata: dict): """ - Replace the metadata associated with a signing keypair + Replace the metadata associated with a signing keypair. Args: verkey: The verification key of the keypair @@ -105,6 +117,7 @@ async def replace_signing_key_metadata(self, verkey: str, metadata: dict): Raises: WalletNotFoundError: if no keypair is associated with the verification key + """ if verkey not in self._keys: raise WalletNotFoundError("Key not found: {}".format(verkey)) @@ -114,7 +127,19 @@ async def create_local_did( self, seed: str = None, did: str = None, metadata: dict = None ) -> DIDInfo: """ - Create and store a new local DID + Create and store a new local DID. + + Args: + seed: Optional seed to use for did + did: The DID to use + metadata: Metadata to store with DID + + Returns: + A `DIDInfo` instance representing the created DID + + Raises: + WalletDuplicateError: If the DID already exists in the wallet + """ seed = validate_seed(seed) or random_seed() verkey, secret = create_keypair(seed) @@ -132,20 +157,43 @@ async def create_local_did( return DIDInfo(did, verkey_enc, self._local_dids[did]["metadata"].copy()) def _get_did_info(self, did: str) -> DIDInfo: - """Convert internal DID record to DIDInfo""" + """ + Convert internal DID record to DIDInfo. + + Args: + did: The DID to get info for + + Returns: + A `DIDInfo` instance for the DID + + """ info = self._local_dids[did] return DIDInfo(did=did, verkey=info["verkey"], metadata=info["metadata"].copy()) async def get_local_dids(self) -> Sequence[DIDInfo]: """ - Get list of defined local DIDs + Get list of defined local DIDs. + + Returns: + A list of locally stored DIDs as `DIDInfo` instances + """ ret = [self._get_did_info(did) for did in self._local_dids] return ret async def get_local_did(self, did: str) -> DIDInfo: """ - Find info for a local DID + Find info for a local DID. + + Args: + did: The DID to get info for + + Returns: + A `DIDInfo` instance representing the found DID + + Raises: + WalletNotFoundError: If the DID is not found + """ if did not in self._local_dids: raise WalletNotFoundError("DID not found: {}".format(did)) @@ -153,7 +201,17 @@ async def get_local_did(self, did: str) -> DIDInfo: async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: """ - Resolve a local DID from a verkey + Resolve a local DID from a verkey. + + Args: + verkey: The verkey to get the local DID for + + Returns: + A `DIDInfo` instance representing the found DID + + Raises: + WalletNotFoundError: If the verkey is not found + """ for did, info in self._local_dids.items(): if info["verkey"] == verkey: @@ -162,7 +220,15 @@ async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: async def replace_local_did_metadata(self, did: str, metadata: dict): """ - Replace metadata for a local DID + Replace metadata for a local DID. + + Args: + did: The DID to replace metadata for + metadata: The new metadata + + Raises: + WalletNotFoundError: If the DID doesn't exist + """ if did not in self._local_dids: raise WalletNotFoundError("Unknown DID: {}".format(did)) @@ -176,7 +242,20 @@ async def create_pairwise( metadata: dict = None, ) -> PairwiseInfo: """ - Create a new pairwise DID for a secure connection + Create a new pairwise DID for a secure connection. + + Args: + their_did: The other party's DID + their_verkey: The other party's verkey + my_did: My DID + metadata: Metadata to store with this relationship + + Returns: + A `PairwiseInfo` object representing the pairwise connection + + Raises: + WalletDuplicateError: If the DID already exists in the wallet + """ if my_did: my_info = await self.get_local_did(my_did) @@ -198,7 +277,16 @@ async def create_pairwise( return self._get_pairwise_info(their_did) def _get_pairwise_info(self, their_did: str) -> PairwiseInfo: - """Convert internal pairwise DID record to PairwiseInfo""" + """ + Convert internal pairwise DID record to `PairwiseInfo`. + + Args: + their_did: The DID to get `PairwiseInfo` for + + Returns: + A `PairwiseInfo` instance + + """ info = self._pair_dids[their_did] return PairwiseInfo( their_did=their_did, @@ -210,14 +298,28 @@ def _get_pairwise_info(self, their_did: str) -> PairwiseInfo: async def get_pairwise_list(self) -> Sequence[PairwiseInfo]: """ - Get list of defined pairwise DIDs + Get list of defined pairwise DIDs. + + Returns: + A list of `PairwiseInfo` instances for all pairwise relationships + """ ret = [self._get_pairwise_info(their_did) for their_did in self._pair_dids] return ret async def get_pairwise_for_did(self, their_did: str) -> PairwiseInfo: """ - Find info for a pairwise DID + Find info for a pairwise DID. + + Args: + their_did: The DID to get a pairwise relationship for + + Returns: + A `PairwiseInfo` instance representing the relationship + + Raises: + WalletNotFoundError: If the DID is unknown + """ if their_did not in self._pair_dids: raise WalletNotFoundError("Unknown target DID: {}".format(their_did)) @@ -225,7 +327,17 @@ async def get_pairwise_for_did(self, their_did: str) -> PairwiseInfo: async def get_pairwise_for_verkey(self, their_verkey: str) -> PairwiseInfo: """ - Resolve a pairwise DID from a verkey + Resolve a pairwise DID from a verkey. + + Args: + their_verkey: The verkey to get a pairwise relationship for + + Returns: + A `PairwiseInfo` instance for the relationship + + Raises: + WalletNotFoundError: If the verkey is not found + """ for did, info in self._pair_dids.items(): if info["their_verkey"] == their_verkey: @@ -234,14 +346,36 @@ async def get_pairwise_for_verkey(self, their_verkey: str) -> PairwiseInfo: async def replace_pairwise_metadata(self, their_did: str, metadata: dict): """ - Replace metadata for a pairwise DID + Replace metadata for a pairwise DID. + + Args: + their_did: The DID to replace metadata for + metadata: The new metadata + + Raises: + WalletNotFoundError: If the DID is unknown + """ if their_did not in self._pair_dids: raise WalletNotFoundError("Unknown target DID: {}".format(their_did)) self._pair_dids[their_did]["metadata"] = metadata.copy() if metadata else {} def _get_private_key(self, verkey: str, long=False) -> bytes: - """Resolve private key for a wallet DID""" + """ + Resolve private key for a wallet DID. + + Args: + verkey: The verkey to lookup + long: + + Returns: + The private key + + + Raises: + WalletError: If the private key is not found + + """ keys_and_dids = list(self._local_dids.values()) + list(self._keys.values()) for info in keys_and_dids: @@ -252,7 +386,19 @@ def _get_private_key(self, verkey: str, long=False) -> bytes: async def sign_message(self, message: bytes, from_verkey: str) -> bytes: """ - Sign a message using the private key associated with a given verkey + Sign a message using the private key associated with a given verkey. + + Args: + message: Message bytes to sign + from_verkey: The verkey to use to sign + + Returns: + A signature + + Raises: + WalletError: If the message is not provided + WalletError: If the verkey is not provided + """ if not message: raise WalletError("Message not provided") @@ -266,7 +412,21 @@ async def verify_message( self, message: bytes, signature: bytes, from_verkey: str ) -> bool: """ - Verify a signature against the public key of the signer + Verify a signature against the public key of the signer. + + Args: + message: Message to verify + signature: Signature to verify + from_verkey: Verkey to use in verification + + Returns: + True if verified, else False + + Raises: + WalletError: If the verkey is not provided + WalletError: If the signature is not provided + WalletError: If the message is not provided + """ if not from_verkey: raise WalletError("Verkey not provided") @@ -282,7 +442,7 @@ async def encrypt_message( self, message: bytes, to_verkey: str, from_verkey: str = None ) -> bytes: """ - Apply auth_crypt or anon_crypt to a message + Apply auth_crypt or anon_crypt to a message. Args: message: The binary message content @@ -292,6 +452,7 @@ async def encrypt_message( Returns: The encrypted message content + """ to_verkey_bytes = b58_to_bytes(to_verkey) if from_verkey: @@ -305,16 +466,18 @@ async def decrypt_message( self, enc_message: bytes, to_verkey: str, use_auth: bool ) -> (bytes, str): """ - Decrypt a message assembled by auth_crypt or anon_crypt + Decrypt a message assembled by auth_crypt or anon_crypt. Args: message: The encrypted message content to_verkey: The verkey of the recipient. If provided then auth_decrypt is used, otherwise anon_decrypt is used. + use_auth: True if you would like to auth_decrypt, False for anon_decrypt Returns: A tuple of the decrypted message content and sender verkey (None for anon_crypt) + """ secret = self._get_private_key(to_verkey) if use_auth: @@ -328,7 +491,16 @@ async def pack_message( self, message: str, to_verkeys: Sequence[str], from_verkey: str = None ) -> bytes: """ - Pack a message for one or more recipients + Pack a message for one or more recipients. + + Args: + message: The message to pack + to_verkeys: List of verkeys to pack for + from_verkey: Sender verkey to pack from + + Returns: + The resulting packed message bytes + """ keys_bin = [b58_to_bytes(key) for key in to_verkeys] secret = self._get_private_key(from_verkey) if from_verkey else None @@ -337,7 +509,18 @@ async def pack_message( async def unpack_message(self, enc_message: bytes) -> (str, str, str): """ - Unpack a message + Unpack a message. + + Args: + enc_message: The packed message bytes + + Returns: + A tuple: (message, from_verkey, to_verkey) + + Raises: + WalletError: If the message is not provided + WalletError: If there is a problem unpacking the message + """ if not enc_message: raise WalletError("Message not provided") diff --git a/agent/indy_catalyst_agent/wallet/crypto.py b/agent/indy_catalyst_agent/wallet/crypto.py index c405a71182..58b345281f 100644 --- a/agent/indy_catalyst_agent/wallet/crypto.py +++ b/agent/indy_catalyst_agent/wallet/crypto.py @@ -1,6 +1,4 @@ -""" -Cryptography functions used by BasicWallet -""" +"""Cryptography functions used by BasicWallet.""" from collections import OrderedDict import json @@ -15,6 +13,8 @@ class PackMessageSchema(Schema): + """Packed message schema.""" + protected = fields.Str(required=True) iv = fields.Str(required=True) tag = fields.Str(required=True) @@ -22,17 +22,23 @@ class PackMessageSchema(Schema): class PackRecipientHeaderSchema(Schema): + """Packed recipient header schema.""" + kid = fields.Str(required=True) sender = fields.Str(required=False, allow_none=True) iv = fields.Str(required=False, allow_none=True) class PackRecipientSchema(Schema): + """Packed recipient schema.""" + encrypted_key = fields.Str(required=True) header = fields.Nested(PackRecipientHeaderSchema(), required=True) class PackRecipientsSchema(Schema): + """Packed recipients schema.""" + enc = fields.Constant("xchacha20poly1305_ietf", required=True) typ = fields.Constant("JWM/1.0", required=True) alg = fields.Str(required=True) @@ -40,7 +46,16 @@ class PackRecipientsSchema(Schema): def create_keypair(seed: bytes = None) -> (bytes, bytes): - """Create a public and private signing keypair from a seed value""" + """ + Create a public and private signing keypair from a seed value. + + Args: + seed: Seed for keypair + + Returns: + A tuple of (public key, secret key) + + """ if not seed: seed = random_seed() pk, sk = pysodium.crypto_sign_seed_keypair(seed) @@ -48,12 +63,27 @@ def create_keypair(seed: bytes = None) -> (bytes, bytes): def random_seed() -> bytes: - """Generate a random seed value""" + """ + Generate a random seed value. + + Returns: + A new random seed + + """ return pysodium.randombytes(pysodium.crypto_secretbox_KEYBYTES) def validate_seed(seed: (str, bytes)) -> bytes: - """Convert a seed parameter to standard format and check length""" + """ + Convert a seed parameter to standard format and check length. + + Args: + seed: The seed to validate + + Returns: + The validated and encoded seed + + """ if not seed: return None if isinstance(seed, str): @@ -69,14 +99,34 @@ def validate_seed(seed: (str, bytes)) -> bytes: def sign_message(message: bytes, secret: bytes) -> bytes: - """Sign a message using a private signing key""" + """ + Sign a message using a private signing key. + + Args: + message: The message to sign + secret: The private signing key + + Returns: + The signature + + """ result = pysodium.crypto_sign(message, secret) sig = result[: pysodium.crypto_sign_BYTES] return sig def verify_signed_message(signed: bytes, verkey: bytes) -> bool: - """Verify a signed message according to a public verification key""" + """ + Verify a signed message according to a public verification key. + + Args: + signed: The signed message + verkey: The verkey to use in verification + + Returns: + True if verified, else False + + """ try: pysodium.crypto_sign_open(signed, verkey) except ValueError: @@ -85,14 +135,34 @@ def verify_signed_message(signed: bytes, verkey: bytes) -> bool: def anon_crypt_message(message: bytes, to_verkey: bytes) -> bytes: - """Apply anon_crypt to a binary message""" + """ + Apply anon_crypt to a binary message. + + Args: + message: The message to encrypt + to_verkey: The verkey to encrypt the message for + + Returns: + The anon encrypted message + + """ pk = pysodium.crypto_sign_pk_to_box_pk(to_verkey) enc_message = pysodium.crypto_box_seal(message, pk) return enc_message def anon_decrypt_message(enc_message: bytes, secret: bytes) -> bytes: - """Apply anon_decrypt to a binary message""" + """ + Apply anon_decrypt to a binary message. + + Args: + enc_message: The encrypted message + secret: The seed to use + + Returns: + The decrypted message + + """ sign_pk, sign_sk = create_keypair(secret) pk = pysodium.crypto_sign_pk_to_box_pk(sign_pk) sk = pysodium.crypto_sign_sk_to_box_sk(sign_sk) @@ -102,7 +172,18 @@ def anon_decrypt_message(enc_message: bytes, secret: bytes) -> bytes: def auth_crypt_message(message: bytes, to_verkey: bytes, from_secret: bytes) -> bytes: - """Apply auth_crypt to a binary message""" + """ + Apply auth_crypt to a binary message. + + Args: + message: The message to encrypt + to_verkey: To recipient's verkey + from_secret: The seed to use + + Returns: + The encrypted message + + """ nonce = pysodium.randombytes(pysodium.crypto_box_NONCEBYTES) target_pk = pysodium.crypto_sign_pk_to_box_pk(to_verkey) sender_pk, sender_sk = create_keypair(from_secret) @@ -121,7 +202,17 @@ def auth_crypt_message(message: bytes, to_verkey: bytes, from_secret: bytes) -> def auth_decrypt_message(enc_message: bytes, secret: bytes) -> (bytes, str): - """Apply auth_decrypt to a binary message""" + """ + Apply auth_decrypt to a binary message. + + Args: + enc_message: The encrypted message + secret: Secret for signing keys + + Returns: + A tuple of (decrypted message, sender verkey) + + """ sign_pk, sign_sk = create_keypair(secret) pk = pysodium.crypto_sign_pk_to_box_pk(sign_pk) sk = pysodium.crypto_sign_sk_to_box_sk(sign_sk) @@ -139,7 +230,17 @@ def auth_decrypt_message(enc_message: bytes, secret: bytes) -> (bytes, str): def prepare_pack_recipient_keys( to_verkeys: Sequence[bytes], from_secret: bytes = None ) -> (str, bytes): - """Assemble the recipients block of a packed message""" + """ + Assemble the recipients block of a packed message. + + Args: + to_verkeys: Verkeys of recipients + from_secret: Secret to use for signing keys + + Returns: + A tuple of (json result, key) + + """ cek = pysodium.crypto_secretstream_xchacha20poly1305_keygen() recips = [] @@ -201,8 +302,21 @@ def locate_pack_recipient_key( recipients: Sequence[dict], find_key: Callable ) -> (bytes, str, str): """ + Locate pack recipient key. + Decode the encryption key and sender verification key from a - corresponding recipient block, if any is defined + corresponding recipient block, if any is defined. + + Args: + recipients: Recipients to locate + find_key: Function used to find private key + + Returns: + A tuple of (cek, sender_vk, recip_vk_b58) + + Raises: + ValueError: If no corresponding recipient key found + """ not_found = [] for recip in recipients: @@ -241,7 +355,16 @@ def encrypt_plaintext( message: str, add_data: bytes, key: bytes ) -> (bytes, bytes, bytes): """ - Encrypt the payload of a packed message + Encrypt the payload of a packed message. + + Args: + message: Message to encrypt + add_data: + key: Key used for encryption + + Returns: + A tuple of (ciphertext, nonce, tag) + """ nonce = pysodium.randombytes(pysodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES) message_bin = message.encode("ascii") @@ -257,7 +380,19 @@ def encrypt_plaintext( def decrypt_plaintext( ciphertext: bytes, recips_bin: bytes, nonce: bytes, key: bytes ) -> str: - """Decrypt the payload of a packed message""" + """ + Decrypt the payload of a packed message. + + Args: + ciphertext: + recips_bin: + nonce: + key: + + Returns: + The decrypted string + + """ output = pysodium.crypto_aead_chacha20poly1305_ietf_decrypt( ciphertext, recips_bin, nonce, key ) @@ -268,7 +403,16 @@ def encode_pack_message( message: str, to_verkeys: Sequence[bytes], from_secret: bytes = None ) -> bytes: """ - Assemble a packed message for a set of recipients, optionally including the sender + Assemble a packed message for a set of recipients, optionally including the sender. + + Args: + message: The message to pack + to_verkeys: The verkeys to pack the message for + from_secret: The sender secret + + Returns: + The encoded message + """ recips_json, cek = prepare_pack_recipient_keys(to_verkeys, from_secret) recips_b64 = bytes_to_b64(recips_json.encode("ascii"), urlsafe=True) @@ -290,8 +434,25 @@ def decode_pack_message( enc_message: bytes, find_key: Callable ) -> (str, Optional[str], str): """ + Decode a packed message. + Disassemble and unencrypt a packed message, returning the message content, - verification key of the sender (if available), and verification key of the recipient + verification key of the sender (if available), and verification key of the + recipient. + + Args: + enc_message: The encrypted message + find_key: Function to retrieve private key + + Returns: + A tuple of (message, sender_vk, recip_vk) + + Raises: + ValueError: If the packed message is invalid + ValueError: If the packed message reipients are invalid + ValueError: If the pack algorithm is unsupported + ValueError: If the sender's public key was not provided + """ try: wrapper = PackMessageSchema().loads(enc_message) diff --git a/agent/indy_catalyst_agent/wallet/error.py b/agent/indy_catalyst_agent/wallet/error.py index 150dadd2b8..1187523957 100644 --- a/agent/indy_catalyst_agent/wallet/error.py +++ b/agent/indy_catalyst_agent/wallet/error.py @@ -1,23 +1,21 @@ -""" -Wallet-related exceptions -""" +"""Wallet-related exceptions.""" from ..error import BaseError class WalletError(BaseError): - """General wallet exception""" + """General wallet exception.""" pass class WalletNotFoundError(WalletError): - """Record not found exception""" + """Record not found exception.""" pass class WalletDuplicateError(WalletError): - """Duplicate record exception""" + """Duplicate record exception.""" pass diff --git a/agent/indy_catalyst_agent/wallet/indy.py b/agent/indy_catalyst_agent/wallet/indy.py index d2d678c1ce..33b2134daf 100644 --- a/agent/indy_catalyst_agent/wallet/indy.py +++ b/agent/indy_catalyst_agent/wallet/indy.py @@ -1,6 +1,4 @@ -""" -Indy implementation of BaseWallet interface -""" +"""Indy implementation of BaseWallet interface.""" import json from typing import Sequence @@ -17,7 +15,7 @@ class IndyWallet(BaseWallet): - """Indy wallet implementation""" + """Indy wallet implementation.""" DEFAULT_FRESHNESS = 0 DEFAULT_KEY = "" @@ -25,6 +23,13 @@ class IndyWallet(BaseWallet): DEFAULT_STORAGE_TYPE = None def __init__(self, config: dict = None): + """ + Initialize a `IndyWallet` instance. + + Args: + config: {name, key, seed, did, auto-create, auto-remove} + + """ if not config: config = {} super(IndyWallet, self).__init__(config) @@ -38,22 +43,46 @@ def __init__(self, config: dict = None): @property def handle(self): - """Get internal wallet reference""" + """ + Get internal wallet reference. + + Returns: + A handle to the wallet + + """ return self._handle @property def opened(self) -> bool: - """Check whether wallet is currently open""" + """ + Check whether wallet is currently open. + + Returns: + True if open, else False + + """ return bool(self._handle) @property def name(self) -> str: - """Accessor for the wallet name""" + """ + Accessor for the wallet name. + + Returns: + The wallet name + + """ return self._name @property def _wallet_config(self) -> dict: - """ """ + """ + Accessor for the wallet config. + + Returns: + The wallet config + + """ return { "id": self._name, "freshness_time": self._freshness_time, @@ -62,7 +91,13 @@ def _wallet_config(self) -> dict: @property def _wallet_access(self) -> dict: - """ """ + """ + Accessor for the wallet access. + + Returns: + The wallet access + + """ return { "key": self._key, # key_derivation_method @@ -71,7 +106,15 @@ def _wallet_access(self) -> dict: async def create(self, replace: bool = False): """ - Create a new wallet + Create a new wallet. + + Args: + replace: Removes the old wallet if True + + Raises: + WalletError: If there was a problem removing the wallet + WalletError: IF there was a libindy error + """ if replace: try: @@ -95,7 +138,12 @@ async def create(self, replace: bool = False): async def remove(self): """ - Remove an existing wallet + Remove an existing wallet. + + Raises: + WalletNotFoundError: If the wallet could not be found + WalletError: If there was an libindy error + """ try: await indy.wallet.delete_wallet( @@ -109,7 +157,14 @@ async def remove(self): async def open(self): """ - Open wallet, removing and/or creating it if so configured + Open wallet, removing and/or creating it if so configured. + + Raises: + WalletError: If wallet not found after creation + WalletNotFoundError: If the wallet is not found + WalletError: If the wallet is already open + WalletError: If there is a libindy error + """ if self.opened: return @@ -140,9 +195,7 @@ async def open(self): raise WalletError(str(x_indy)) async def close(self): - """ - Close previously-opened wallet, removing it if so configured - """ + """Close previously-opened wallet, removing it if so configured.""" if self._handle: await indy.wallet.close_wallet(self._handle) if self._auto_remove: @@ -153,15 +206,19 @@ async def create_signing_key( self, seed: str = None, metadata: dict = None ) -> KeyInfo: """ - Create a new public/private signing keypair + Create a new public/private signing keypair. Args: + seed: Seed for key metadata: Optional metadata to store with the keypair - Returns: a `KeyInfo` representing the new record + Returns: + A `KeyInfo` representing the new record Raises: WalletDuplicateError: If the resulting verkey already exists in the wallet + WalletError: If there is a libindy error + """ args = {} if seed: @@ -182,15 +239,18 @@ async def create_signing_key( async def get_signing_key(self, verkey: str) -> KeyInfo: """ - Fetch info for a signing keypair + Fetch info for a signing keypair. Args: verkey: The verification key of the keypair - Returns: a `KeyInfo` representing the keypair + Returns: + A `KeyInfo` representing the keypair Raises: - WalletNotFoundError: if no keypair is associated with the verification key + WalletNotFoundError: If no keypair is associated with the verification key + WalletError: If there is a libindy error + """ try: metadata = await indy.crypto.get_key_metadata(self.handle, verkey) @@ -203,7 +263,7 @@ async def get_signing_key(self, verkey: str) -> KeyInfo: async def replace_signing_key_metadata(self, verkey: str, metadata: dict): """ - Replace the metadata associated with a signing keypair + Replace the metadata associated with a signing keypair. Args: verkey: The verification key of the keypair @@ -211,6 +271,7 @@ async def replace_signing_key_metadata(self, verkey: str, metadata: dict): Raises: WalletNotFoundError: if no keypair is associated with the verification key + """ meta_json = json.dumps(metadata or {}) await self.get_signing_key(verkey) # throw exception if key is undefined @@ -220,7 +281,20 @@ async def create_local_did( self, seed: str = None, did: str = None, metadata: dict = None ) -> DIDInfo: """ - Create and store a new local DID + Create and store a new local DID. + + Args: + seed: Optional seed to use for did + did: The DID to use + metadata: Metadata to store with DID + + Returns: + A `DIDInfo` instance representing the created DID + + Raises: + WalletDuplicateError: If the DID already exists in the wallet + WalletError: If there is a libindy error + """ cfg = {} if seed: @@ -242,7 +316,11 @@ async def create_local_did( async def get_local_dids(self) -> Sequence[DIDInfo]: """ - Get list of defined local DIDs + Get list of defined local DIDs. + + Returns: + A list of locally stored DIDs as `DIDInfo` instances + """ info_json = await indy.did.list_my_dids_with_meta(self.handle) info = json.loads(info_json) @@ -259,8 +337,20 @@ async def get_local_dids(self) -> Sequence[DIDInfo]: async def get_local_did(self, did: str) -> DIDInfo: """ - Find info for a local DID + Find info for a local DID. + + Args: + did: The DID to get info for + + Returns: + A `DIDInfo` instance representing the found DID + + Raises: + WalletNotFoundError: If the DID is not found + WalletError: If there is a libindy error + """ + try: info_json = await indy.did.get_my_did_with_meta(self.handle, did) except IndyError as x_indy: @@ -277,8 +367,19 @@ async def get_local_did(self, did: str) -> DIDInfo: async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: """ - Resolve a local DID from a verkey + Resolve a local DID from a verkey. + + Args: + verkey: The verkey to get the local DID for + + Returns: + A `DIDInfo` instance representing the found DID + + Raises: + WalletNotFoundError: If the verkey is not found + """ + dids = await self.get_local_dids() for info in dids: if info.verkey == verkey: @@ -287,7 +388,12 @@ async def get_local_did_for_verkey(self, verkey: str) -> DIDInfo: async def replace_local_did_metadata(self, did: str, metadata: dict): """ - Replace metadata for a local DID + Replace metadata for a local DID. + + Args: + did: The DID to replace metadata for + metadata: The new metadata + """ meta_json = json.dumps(metadata or {}) await self.get_local_did(did) # throw exception if undefined @@ -301,7 +407,21 @@ async def create_pairwise( metadata: dict = None, ) -> PairwiseInfo: """ - Create a new pairwise DID for a secure connection + Create a new pairwise DID for a secure connection. + + Args: + their_did: The other party's DID + their_verkey: The other party's verkey + my_did: My DID + metadata: Metadata to store with this relationship + + Returns: + A `PairwiseInfo` object representing the pairwise connection + + Raises: + WalletError: If there is a libindy error + WalletDuplicateError: If the DID already exists in the wallet + """ # store their DID info in wallet @@ -353,7 +473,14 @@ async def create_pairwise( def _make_pairwise_info(self, result: dict, their_did: str = None) -> PairwiseInfo: """ - Convert Indy pairwise info into PairwiseInfo record + Convert Indy pairwise info into PairwiseInfo record. + + Args: + result: + their_did + + Returns: + A new `PairwiseInfo` instance """ meta = result["metadata"] and json.loads(result["metadata"]) or {} if "custom" not in meta: diff --git a/agent/indy_catalyst_agent/wallet/util.py b/agent/indy_catalyst_agent/wallet/util.py index 8623b612a2..8e571e0402 100644 --- a/agent/indy_catalyst_agent/wallet/util.py +++ b/agent/indy_catalyst_agent/wallet/util.py @@ -1,38 +1,28 @@ -""" -Wallet utility functions -""" +"""Wallet utility functions.""" import base58 import base64 def b64_to_bytes(val: str, urlsafe=False) -> bytes: - """ - Convert a base 64 string to bytes - """ + """Convert a base 64 string to bytes.""" if urlsafe: return base64.urlsafe_b64decode(val) return base64.b64decode(val) def bytes_to_b64(val: bytes, urlsafe=False) -> str: - """ - Convert a byte string to base 64 - """ + """Convert a byte string to base 64.""" if urlsafe: return base64.urlsafe_b64encode(val).decode("ascii") return base64.b64encode(val).decode("ascii") def b58_to_bytes(val: str) -> bytes: - """ - Convert a base 58 string to bytes - """ + """Convert a base 58 string to bytes.""" return base58.b58decode(val) def bytes_to_b58(val: bytes) -> str: - """ - Convert a byte string to base 58 - """ + """Convert a byte string to base 58.""" return base58.b58encode(val).decode("ascii") diff --git a/agent/requirements.txt b/agent/requirements.txt index a2ea9ccb08..d8ba142a80 100644 --- a/agent/requirements.txt +++ b/agent/requirements.txt @@ -1,4 +1,4 @@ -aiohttp>=3.5.0<3.6 +aiohttp==3.5.4 base58 marshmallow==3.0.0rc3 msgpack>=0.6.1<0.7 From 89f9a38123d1bd30bd3f5692683971205d04cfe7 Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Mon, 25 Feb 2019 15:51:36 -0800 Subject: [PATCH 7/8] Docstring cleanup Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/classloader.py | 2 +- agent/indy_catalyst_agent/connection.py | 8 +- .../messaging/agent_message.py | 1 + .../messages/connection_invitation.py | 4 +- .../messaging/request_context.py | 45 +++++-- .../trustping/handlers/ping_handler.py | 12 ++ .../handlers/ping_response_handler.py | 13 ++ .../messaging/trustping/message_types.py | 9 +- .../messaging/trustping/messages/ping.py | 15 ++- .../trustping/messages/ping_response.py | 12 +- .../models/field_signature.py | 5 +- agent/indy_catalyst_agent/wallet/indy.py | 112 ++++++++++++++++-- agent/requirements.dev.txt | 2 +- 13 files changed, 195 insertions(+), 45 deletions(-) diff --git a/agent/indy_catalyst_agent/classloader.py b/agent/indy_catalyst_agent/classloader.py index 6037ec6358..da79555271 100644 --- a/agent/indy_catalyst_agent/classloader.py +++ b/agent/indy_catalyst_agent/classloader.py @@ -42,7 +42,7 @@ def load(self, module_path, load_relative=False): Args: module_path: Dotted path to module load_relative: Should the method check in the - configured base path for relative import + configured base path for relative import Return: The loaded class diff --git a/agent/indy_catalyst_agent/connection.py b/agent/indy_catalyst_agent/connection.py index d2041e519b..ddb1b1013c 100644 --- a/agent/indy_catalyst_agent/connection.py +++ b/agent/indy_catalyst_agent/connection.py @@ -583,9 +583,11 @@ async def expand_message( if isinstance(message_body, bytes): try: - message_json, from_verkey, to_verkey = await self.context.wallet.unpack_message( - message_body - ) + ( + message_json, + from_verkey, + to_verkey, + ) = await self.context.wallet.unpack_message(message_body) except WalletError: self._logger.debug("Message unpack failed, trying JSON") diff --git a/agent/indy_catalyst_agent/messaging/agent_message.py b/agent/indy_catalyst_agent/messaging/agent_message.py index 06303ae5f1..4f7c871c1d 100644 --- a/agent/indy_catalyst_agent/messaging/agent_message.py +++ b/agent/indy_catalyst_agent/messaging/agent_message.py @@ -225,6 +225,7 @@ async def verify_signatures(self, wallet: BaseWallet) -> bool: Returns: True if all signatures verify, else false + """ for sig in self._message_signatures.values(): if not await sig.verify(wallet): diff --git a/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py b/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py index 395a5e11c3..c91540acaa 100644 --- a/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py +++ b/agent/indy_catalyst_agent/messaging/connections/messages/connection_invitation.py @@ -1,6 +1,4 @@ -""" -Represents an invitation message for establishing connection. -""" +"""Represents an invitation message for establishing connection.""" from typing import Sequence from urllib.parse import parse_qs, urljoin, urlparse diff --git a/agent/indy_catalyst_agent/messaging/request_context.py b/agent/indy_catalyst_agent/messaging/request_context.py index 8175f39059..28c01c82fd 100644 --- a/agent/indy_catalyst_agent/messaging/request_context.py +++ b/agent/indy_catalyst_agent/messaging/request_context.py @@ -48,28 +48,44 @@ def copy(self) -> "RequestContext": @property def connection_active(self) -> bool: - """Accessor for the flag indicating an active connection with the sender""" + """ + Accessor for the flag indicating an active connection with the sender. + + Returns: + True if the connection is active, else False + + """ return self._connection_active @connection_active.setter def connection_active(self, active: bool): - """Setter for the flag indicating an active connection with the sender + """ + Setter for the flag indicating an active connection with the sender. - :param active: bool: + Args: + active: The new active value """ self._connection_active = active @property def connection_target(self) -> ConnectionTarget: - """Accessor for the ConnectionTarget associated with the current connection""" + """ + Accessor for the ConnectionTarget associated with the current connection. + + Returns: + The connection target for this connection + + """ return self._connection_target @connection_target.setter def connection_target(self, target: ConnectionTarget): - """Setter for the ConnectionTarget associated with the current connection + """ + Setter for the ConnectionTarget associated with the current connection. - :param target: str: + Args: + The new connection target """ self._connection_target = target @@ -199,14 +215,22 @@ def recipient_verkey(self, verkey: str): @property def sender_did(self) -> str: - """Accessor for the sender DID which corresponds with the verkey""" + """ + Accessor for the sender DID which corresponds with the verkey. + + Returns: + The sender did + + """ return self._sender_did @sender_did.setter def sender_did(self, did: str): - """Setter for the sender DID which corresponds with the verkey + """ + Setter for the sender DID which corresponds with the verkey. - :param did: str: + Args: + The new sender did """ self._sender_did = did @@ -247,7 +271,7 @@ def transport_type(self) -> str: @transport_type.setter def transport_type(self, transport: str): """ - Setter for the transport type used to receive the message + Setter for the transport type used to receive the message. Args: transport: This context's new transport @@ -352,6 +376,7 @@ def __repr__(self) -> str: Returns: A human readable representation of this object + """ skip = ("_logger",) items = ( diff --git a/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_handler.py b/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_handler.py index a369fa5d0e..b989554ce2 100644 --- a/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_handler.py +++ b/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_handler.py @@ -1,3 +1,5 @@ +"""Ping handler.""" + from ...base_handler import BaseHandler, BaseResponder, RequestContext from ..messages.ping import Ping from ..messages.ping_response import PingResponse @@ -5,7 +7,17 @@ class PingHandler(BaseHandler): + """Ping handler class.""" + async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Handle ping message. + + Args: + context: Request context + responder: Responder used to reply + + """ self._logger.debug(f"PingHandler called with context {context}") assert isinstance(context.message, Ping) diff --git a/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_response_handler.py b/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_response_handler.py index 88f7979a52..3a71f6f120 100644 --- a/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_response_handler.py +++ b/agent/indy_catalyst_agent/messaging/trustping/handlers/ping_response_handler.py @@ -1,9 +1,22 @@ +"""Ping response handler.""" + from ...base_handler import BaseHandler, BaseResponder, RequestContext from ..messages.ping_response import PingResponse class PingResponseHandler(BaseHandler): + """Ping response handler class.""" + async def handle(self, context: RequestContext, responder: BaseResponder): + """ + Handle ping response message. + + Args: + context: Request context + responder: Responder used to reply + + """ + self._logger.debug("PingResponseHandler called with context: %s", context) assert isinstance(context.message, PingResponse) diff --git a/agent/indy_catalyst_agent/messaging/trustping/message_types.py b/agent/indy_catalyst_agent/messaging/trustping/message_types.py index 17ac9c1901..db3a9f328b 100644 --- a/agent/indy_catalyst_agent/messaging/trustping/message_types.py +++ b/agent/indy_catalyst_agent/messaging/trustping/message_types.py @@ -1,6 +1,4 @@ -""" -Message type identifiers for Trust Pings -""" +"""Message type identifiers for Trust Pings.""" MESSAGE_FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/trustping/1.0/" @@ -8,8 +6,7 @@ PING_RESPONSE = f"{MESSAGE_FAMILY}ping_response" MESSAGE_TYPES = { - PING: "indy_catalyst_agent.messaging.trustping." - + "messages.ping.Ping", + PING: "indy_catalyst_agent.messaging.trustping." + "messages.ping.Ping", PING_RESPONSE: "indy_catalyst_agent.messaging.trustping." - + "messages.ping_response.PingResponse" + + "messages.ping_response.PingResponse", } diff --git a/agent/indy_catalyst_agent/messaging/trustping/messages/ping.py b/agent/indy_catalyst_agent/messaging/trustping/messages/ping.py index 28a4da134a..9c48381c0f 100644 --- a/agent/indy_catalyst_agent/messaging/trustping/messages/ping.py +++ b/agent/indy_catalyst_agent/messaging/trustping/messages/ping.py @@ -1,23 +1,28 @@ -""" -Represents a trust ping message -""" +"""Represents a trust ping message.""" from ...agent_message import AgentMessage, AgentMessageSchema from ..message_types import PING HANDLER_CLASS = ( - "indy_catalyst_agent.messaging.trustping." - + "handlers.ping_handler.PingHandler" + "indy_catalyst_agent.messaging.trustping." + "handlers.ping_handler.PingHandler" ) class Ping(AgentMessage): + """Class representing a trustping message.""" + class Meta: + """Ping metadata.""" + handler_class = HANDLER_CLASS message_type = PING schema_class = "PingSchema" class PingSchema(AgentMessageSchema): + """Schema for Ping class.""" + class Meta: + """PingSchema metadata.""" + model_class = Ping diff --git a/agent/indy_catalyst_agent/messaging/trustping/messages/ping_response.py b/agent/indy_catalyst_agent/messaging/trustping/messages/ping_response.py index 5a5e46f725..d28374891f 100644 --- a/agent/indy_catalyst_agent/messaging/trustping/messages/ping_response.py +++ b/agent/indy_catalyst_agent/messaging/trustping/messages/ping_response.py @@ -1,6 +1,4 @@ -""" -Represents an response to a trust ping message -""" +"""Represents an response to a trust ping message.""" from ...agent_message import AgentMessage, AgentMessageSchema from ..message_types import PING_RESPONSE @@ -12,12 +10,20 @@ class PingResponse(AgentMessage): + """Class representing a ping response.""" + class Meta: + """PingResponse metadata.""" + handler_class = HANDLER_CLASS message_type = PING_RESPONSE schema_class = "PingResponseSchema" class PingResponseSchema(AgentMessageSchema): + """PingResponse schema.""" + class Meta: + """PingResponseSchema metadata.""" + model_class = PingResponse diff --git a/agent/indy_catalyst_agent/models/field_signature.py b/agent/indy_catalyst_agent/models/field_signature.py index 06ea5b3cbc..7631321846 100644 --- a/agent/indy_catalyst_agent/models/field_signature.py +++ b/agent/indy_catalyst_agent/models/field_signature.py @@ -1,6 +1,4 @@ -""" -Model and schema for working with field signatures within message bodies -""" +"""Model and schema for working with field signatures within message bodies.""" import json @@ -88,6 +86,7 @@ def decode(self) -> (object, int): Returns: A tuple of (decoded message, timestamp) + """ msg_bin = b64_to_bytes(self.sig_data, urlsafe=True) timestamp, = struct.unpack_from("!Q", msg_bin, 0) diff --git a/agent/indy_catalyst_agent/wallet/indy.py b/agent/indy_catalyst_agent/wallet/indy.py index 33b2134daf..e1ec7b6d01 100644 --- a/agent/indy_catalyst_agent/wallet/indy.py +++ b/agent/indy_catalyst_agent/wallet/indy.py @@ -481,6 +481,7 @@ def _make_pairwise_info(self, result: dict, their_did: str = None) -> PairwiseIn Returns: A new `PairwiseInfo` instance + """ meta = result["metadata"] and json.loads(result["metadata"]) or {} if "custom" not in meta: @@ -496,7 +497,11 @@ def _make_pairwise_info(self, result: dict, their_did: str = None) -> PairwiseIn async def get_pairwise_list(self) -> Sequence[PairwiseInfo]: """ - Get list of defined pairwise DIDs + Get list of defined pairwise DIDs. + + Returns: + A list of `PairwiseInfo` instances for all pairwise relationships + """ pairs_json = await indy.pairwise.list_pairwise(self.handle) pairs = json.loads(pairs_json) @@ -510,7 +515,18 @@ async def get_pairwise_list(self) -> Sequence[PairwiseInfo]: async def get_pairwise_for_did(self, their_did: str) -> PairwiseInfo: """ - Find info for a pairwise DID + Find info for a pairwise DID. + + Args: + their_did: The DID to get a pairwise relationship for + + Returns: + A `PairwiseInfo` instance representing the relationship + + Raises: + WalletNotFoundError: If no pairwise DID defined for target + WalletNotFoundError: If no pairwise DID defined for target + """ try: pair_json = await indy.pairwise.get_pairwise(self.handle, their_did) @@ -531,7 +547,17 @@ async def get_pairwise_for_did(self, their_did: str) -> PairwiseInfo: async def get_pairwise_for_verkey(self, their_verkey: str) -> PairwiseInfo: """ - Resolve a pairwise DID from a verkey + Resolve a pairwise DID from a verkey. + + Args: + their_verkey: The verkey to get a pairwise relationship for + + Returns: + A `PairwiseInfo` instance for the relationship + + Raises: + WalletNotFoundError: If no pairwise DID is defined for verkey + """ dids = await self.get_pairwise_list() for info in dids: @@ -543,7 +569,12 @@ async def get_pairwise_for_verkey(self, their_verkey: str) -> PairwiseInfo: async def replace_pairwise_metadata(self, their_did: str, metadata: dict): """ - Replace metadata for a pairwise DID + Replace metadata for a pairwise DID. + + Args: + their_did: The DID to replace metadata for + metadata: The new metadata + """ info = await self.get_pairwise_for_did( their_did @@ -555,7 +586,20 @@ async def replace_pairwise_metadata(self, their_did: str, metadata: dict): async def sign_message(self, message: bytes, from_verkey: str) -> bytes: """ - Sign a message using the private key associated with a given verkey + Sign a message using the private key associated with a given verkey. + + Args: + message: Message bytes to sign + from_verkey: The verkey to use to sign + + Returns: + A signature + + Raises: + WalletError: If the message is not provided + WalletError: If the verkey is not provided + WalletError: If a libindy error occurs + """ if not message: raise WalletError("Message not provided") @@ -571,7 +615,22 @@ async def verify_message( self, message: bytes, signature: bytes, from_verkey: str ) -> bool: """ - Verify a signature against the public key of the signer + Verify a signature against the public key of the signer. + + Args: + message: Message to verify + signature: Signature to verify + from_verkey: Verkey to use in verification + + Returns: + True if verified, else False + + Raises: + WalletError: If the verkey is not provided + WalletError: If the signature is not provided + WalletError: If the message is not provided + WalletError: If a libindy error occurs + """ if not from_verkey: raise WalletError("Verkey not provided") @@ -592,7 +651,7 @@ async def encrypt_message( self, message: bytes, to_verkey: str, from_verkey: str = None ) -> bytes: """ - Apply auth_crypt or anon_crypt to a message + Apply auth_crypt or anon_crypt to a message. Args: message: The binary message content @@ -602,6 +661,10 @@ async def encrypt_message( Returns: The encrypted message content + + Raises: + WalletError: If a libindy error occurs + """ if from_verkey: try: @@ -621,16 +684,21 @@ async def decrypt_message( self, enc_message: bytes, to_verkey: str, use_auth: bool ) -> (bytes, str): """ - Decrypt a message assembled by auth_crypt or anon_crypt + Decrypt a message assembled by auth_crypt or anon_crypt. Args: message: The encrypted message content to_verkey: The verkey of the recipient. If provided then auth_decrypt is used, otherwise anon_decrypt is used. + use_auth: True if you would like to auth_decrypt, False for anon_decrypt Returns: A tuple of the decrypted message content and sender verkey (None for anon_crypt) + + Raises: + WalletError: If a libindy error occurs + """ if use_auth: try: @@ -653,7 +721,20 @@ async def pack_message( self, message: str, to_verkeys: Sequence[str], from_verkey: str = None ) -> bytes: """ - Pack a message for one or more recipients + Pack a message for one or more recipients. + + Args: + message: The message to pack + to_verkeys: List of verkeys to pack for + from_verkey: Sender verkey to pack from + + Returns: + The resulting packed message bytes + + Raises: + WalletError: If no message is provided + WalletError: If a libindy error occurs + """ if message is None: raise WalletError("Message not provided") @@ -667,7 +748,18 @@ async def pack_message( async def unpack_message(self, enc_message: bytes) -> (str, str, str): """ - Unpack a message + Unpack a message. + + Args: + enc_message: The packed message bytes + + Returns: + A tuple: (message, from_verkey, to_verkey) + + Raises: + WalletError: If the message is not provided + WalletError: If a libindy error occurs + """ if not enc_message: raise WalletError("Message not provided") diff --git a/agent/requirements.dev.txt b/agent/requirements.dev.txt index ccec059582..8cd7e5f215 100644 --- a/agent/requirements.dev.txt +++ b/agent/requirements.dev.txt @@ -4,6 +4,6 @@ pytest-cov==2.6.1 pytest-flake8==1.0.4 flake8==3.7.5 -flake8-rst-docstrings==0.0.8 +# flake8-rst-docstrings==0.0.8 flake8-docstrings==1.3.0 flake8-rst==0.7.1 \ No newline at end of file From d04d5e3509ae36fb76f7d4b092c4836e69e2de35 Mon Sep 17 00:00:00 2001 From: Nicholas Rempel Date: Tue, 26 Feb 2019 11:55:40 -0800 Subject: [PATCH 8/8] More docs Signed-off-by: Nicholas Rempel --- agent/indy_catalyst_agent/storage/base.py | 139 +++++++++++++++--- agent/indy_catalyst_agent/storage/basic.py | 129 +++++++++++++--- agent/indy_catalyst_agent/storage/error.py | 10 +- agent/indy_catalyst_agent/storage/indy.py | 136 ++++++++++++++--- agent/indy_catalyst_agent/storage/record.py | 8 +- .../transport/inbound/base.py | 11 ++ .../transport/inbound/http.py | 46 +++++- .../transport/inbound/manager.py | 17 ++- .../transport/inbound/ws.py | 33 +++++ .../transport/outbound/base.py | 21 +++ .../transport/outbound/error.py | 0 .../transport/outbound/http.py | 15 +- .../transport/outbound/manager.py | 39 ++++- .../transport/outbound/message.py | 2 + .../transport/outbound/queue/base.py | 14 +- .../transport/outbound/queue/basic.py | 22 ++- .../transport/outbound/ws.py | 16 +- agent/indy_catalyst_agent/wallet/base.py | 1 + agent/setup.cfg | 1 + 19 files changed, 581 insertions(+), 79 deletions(-) delete mode 100644 agent/indy_catalyst_agent/transport/outbound/error.py diff --git a/agent/indy_catalyst_agent/storage/base.py b/agent/indy_catalyst_agent/storage/base.py index 9990d188d5..1ed150e8ba 100644 --- a/agent/indy_catalyst_agent/storage/base.py +++ b/agent/indy_catalyst_agent/storage/base.py @@ -1,6 +1,4 @@ -""" -Abstract base classes for non-secrets storage -""" +"""Abstract base classes for non-secrets storage.""" from abc import ABC, abstractmethod from typing import Mapping, Sequence @@ -11,12 +9,16 @@ class BaseStorage(ABC): - """Abstract Non-Secrets interface""" + """Abstract Non-Secrets interface.""" @abstractmethod async def add_record(self, record: StorageRecord): """ - Add a new record to the store + Add a new record to the store. + + Args: + record: `StorageRecord` to be stored + """ # indy_add_wallet_record pass @@ -24,14 +26,27 @@ async def add_record(self, record: StorageRecord): @abstractmethod async def get_record(self, record_type: str, record_id: str) -> StorageRecord: """ - Fetch a record from the store by type and ID + Fetch a record from the store by type and ID. + + Args: + record_type: The record type + record_id: The record id + + Returns: + A `StorageRecord` instance + """ pass @abstractmethod async def update_record_value(self, record: StorageRecord, value: str): """ - Update an existing stored record's value + Update an existing stored record's value. + + Args: + record: `StorageRecord` to update + value: The new value + """ # indy_update_wallet_record_value pass @@ -39,7 +54,12 @@ async def update_record_value(self, record: StorageRecord, value: str): @abstractmethod async def update_record_tags(self, record: StorageRecord, tags: Mapping): """ - Update an existing stored record's tags + Update an existing stored record's tags. + + Args: + record: `StorageRecord` to update + tags: New tags + """ # indy_update_wallet_record_tags pass @@ -49,13 +69,25 @@ async def delete_record_tags( self, record: StorageRecord, tags: (Sequence, Mapping) ): """ - Update an existing stored record's tags + Update an existing stored record's tags. + + Args: + record: `StorageRecord` to delete + tags: Tags + """ # indy_delete_wallet_record_tags pass @abstractmethod async def delete_record(self, record: StorageRecord): + """ + Delete a record. + + Args: + record: `StorageRecord` to delete + + """ # indy_delete_wallet_record pass @@ -63,14 +95,27 @@ async def delete_record(self, record: StorageRecord): def search_records( self, type_filter: str, tag_query: Mapping = None, page_size: int = None ) -> "BaseStorageRecordSearch": + """ + Search stored records. + + Args: + type_filter: Filter string + tag_query: Tags to query + page_size: Page size + + Returns: + An instance of `BaseStorageRecordSearch` + + """ pass def __repr__(self) -> str: + """Human readable representation of a `BaseStorage` implementation.""" return "<{}>".format(self.__class__.__name__) class BaseStorageRecordSearch(ABC): - """Represent an active stored records search""" + """Represent an active stored records search.""" def __init__( self, @@ -79,6 +124,16 @@ def __init__( tag_query: Mapping, page_size: int = None, ): + """ + Initialize a `BaseStorageRecordSearch` instance. + + Args: + store: `BaseStorage` to search + type_filter: Filter string + tag_query: Tags to search + page_size: Size of page to return + + """ self._buffer = None self._page_size = page_size self._store = store @@ -87,67 +142,104 @@ def __init__( @property def handle(self): - """ """ + """Handle a search request.""" return None @property @abstractmethod def opened(self) -> bool: - """Accessor for open state""" + """ + Accessor for open state. + + Returns: + True if opened, else False + + """ return False @property def page_size(self): - """ """ + """ + Accessor for page size. + + Returns: + The page size + + """ return self._page_size or DEFAULT_PAGE_SIZE @property def store(self) -> BaseStorage: - """ """ + """ + `BaseStorage` backend for this implementation. + + Returns: + The `BaseStorage` implementation being used + + """ return self._store @property def tag_query(self): - """ """ + """ + Accessor for tag query. + + Returns: + The tag query + + """ return self._tag_query @property def type_filter(self): - """ """ + """ + Accessor for type filter. + + Returns: + The type filter + + """ return self._type_filter @abstractmethod async def fetch(self, max_count: int) -> Sequence[StorageRecord]: """ - Fetch the next list of results from the store + Fetch the next list of results from the store. + + Args: + max_count: Max number of records to return + + Returns: + A list of `StorageRecord`s + """ pass @abstractmethod async def open(self): - """ - Start the search query - """ + """Start the search query.""" pass @abstractmethod async def close(self): - """ - Dispose of the search query - """ + """Dispose of the search query.""" pass async def __aenter__(self): + """Context manager enter.""" await self.open() return self async def __aexit__(self, exc_type, exc, tb): + """Context manager exit.""" await self.close() def __aiter__(self): + """Async iterator magic method.""" return self async def __anext__(self): + """Async iterator magic method.""" if not self.opened: await self.open() if not self._buffer: @@ -161,4 +253,5 @@ async def __anext__(self): raise StopAsyncIteration def __repr__(self) -> str: + """Human readable representation of `BaseStorageRecordSearch`.""" return "<{}>".format(self.__class__.__name__) diff --git a/agent/indy_catalyst_agent/storage/basic.py b/agent/indy_catalyst_agent/storage/basic.py index 12ebeb9449..7f1893e2a5 100644 --- a/agent/indy_catalyst_agent/storage/basic.py +++ b/agent/indy_catalyst_agent/storage/basic.py @@ -1,6 +1,4 @@ -""" -Basic in-memory storage implementation (non-wallet) -""" +"""Basic in-memory storage implementation (non-wallet).""" from collections import OrderedDict from typing import Mapping, Sequence @@ -12,12 +10,29 @@ class BasicStorage(BaseStorage): + """Basic in-memory storage class.""" + def __init__(self, _wallet: BaseWallet = None): + """ + Initialize a `BasicStorage` instance. + + Args: + _wallet: The wallet implementation to use + + """ self._records = OrderedDict() async def add_record(self, record: StorageRecord): """ - Add a new record to the store + Add a new record to the store. + + Args: + record: `StorageRecord` to be stored + + Raises: + StorageError: If no record is provided + StorageError: If the record has no ID + """ if not record: raise StorageError("No record provided") @@ -27,7 +42,18 @@ async def add_record(self, record: StorageRecord): async def get_record(self, record_type: str, record_id: str) -> StorageRecord: """ - Fetch a record from the store by ID + Fetch a record from the store by type and ID. + + Args: + record_type: The record type + record_id: The record id + + Returns: + A `StorageRecord` instance + + Raises: + StorageNotFoundError: If the record is not found + """ row = self._records.get(record_id) if row and row.type == record_type: @@ -37,7 +63,15 @@ async def get_record(self, record_type: str, record_id: str) -> StorageRecord: async def update_record_value(self, record: StorageRecord, value: str): """ - Update an existing stored record's value + Update an existing stored record's value. + + Args: + record: `StorageRecord` to update + value: The new value + + Raises: + StorageNotFoundError: If record not found + """ oldrec = self._records.get(record.id) if not oldrec: @@ -46,7 +80,15 @@ async def update_record_value(self, record: StorageRecord, value: str): async def update_record_tags(self, record: StorageRecord, tags: Mapping): """ - Update an existing stored record's tags + Update an existing stored record's tags. + + Args: + record: `StorageRecord` to update + tags: New tags + + Raises: + StorageNotFoundError: If record not found + """ oldrec = self._records.get(record.id) if not oldrec: @@ -57,7 +99,15 @@ async def delete_record_tags( self, record: StorageRecord, tags: (Sequence, Mapping) ): """ - Update an existing stored record's tags + Update an existing stored record's tags. + + Args: + record: `StorageRecord` to delete + tags: Tags + + Raises: + StorageNotFoundError: If record not found + """ oldrec = self._records.get(record.id) if not oldrec: @@ -70,6 +120,16 @@ async def delete_record_tags( self._records[record.id] = oldrec._replace(tags=newtags) async def delete_record(self, record: StorageRecord): + """ + Delete a record. + + Args: + record: `StorageRecord` to delete + + Raises: + StorageNotFoundError: If record not found + + """ if record.id not in self._records: raise StorageNotFoundError("Record not found: {}".format(record.id)) del self._records[record.id] @@ -77,10 +137,24 @@ async def delete_record(self, record: StorageRecord): def search_records( self, type_filter: str, tag_query: Mapping = None, page_size: int = None ) -> "BasicStorageRecordSearch": + """ + Search stored records. + + Args: + type_filter: Filter string + tag_query: Tags to query + page_size: Page size + + Returns: + An instance of `BaseStorageRecordSearch` + + """ return BasicStorageRecordSearch(self, type_filter, tag_query, page_size) class BasicStorageRecordSearch(BaseStorageRecordSearch): + """Represent an active stored records search.""" + def __init__( self, store: BasicStorage, @@ -88,6 +162,16 @@ def __init__( tag_query: Mapping, page_size: int = None, ): + """ + Initialize a `BasicStorageRecordSearch` instance. + + Args: + store: `BaseStorage` to search + type_filter: Filter string + tag_query: Tags to search + page_size: Size of page to return + + """ super(BasicStorageRecordSearch, self).__init__( store, type_filter, tag_query, page_size ) @@ -96,13 +180,28 @@ def __init__( @property def opened(self) -> bool: - """Accessor for open state""" + """ + Accessor for open state. + + Returns: + True if opened, else False + + """ return self._cache is not None async def fetch(self, max_count: int) -> Sequence[StorageRecord]: """ - Fetch the next list of results from the store - TODO: implement tag filtering + Fetch the next list of results from the store. + + Args: + max_count: Max number of records to return + + Returns: + A list of `StorageRecord`s + + Raises: + StorageSearchError: If the search query has not been opened + """ if not self.opened: raise StorageSearchError("Search query has not been opened") @@ -121,14 +220,10 @@ async def fetch(self, max_count: int) -> Sequence[StorageRecord]: return ret async def open(self): - """ - Start the search query - """ + """Start the search query.""" self._cache = self._store._records.copy() self._iter = iter(self._cache) async def close(self): - """ - Dispose of the search query - """ + """Dispose of the search query.""" self._cache = None diff --git a/agent/indy_catalyst_agent/storage/error.py b/agent/indy_catalyst_agent/storage/error.py index 3d2af0f559..9a102f95de 100644 --- a/agent/indy_catalyst_agent/storage/error.py +++ b/agent/indy_catalyst_agent/storage/error.py @@ -1,17 +1,15 @@ -""" -Storage-related exceptions -""" +"""Storage-related exceptions.""" from ..error import BaseError class StorageError(BaseError): - """Base class for Storage errors""" + """Base class for Storage errors.""" class StorageNotFoundError(StorageError): - """Record not found in storage""" + """Record not found in storage.""" class StorageSearchError(StorageError): - """General exception during record search""" + """General exception during record search.""" diff --git a/agent/indy_catalyst_agent/storage/indy.py b/agent/indy_catalyst_agent/storage/indy.py index 6d77bb5ac4..dd7d3e36b1 100644 --- a/agent/indy_catalyst_agent/storage/indy.py +++ b/agent/indy_catalyst_agent/storage/indy.py @@ -1,6 +1,4 @@ -""" -Indy implementation of BaseStorage interface -""" +"""Indy implementation of BaseStorage interface.""" import json from typing import Mapping, Sequence @@ -24,19 +22,30 @@ def _validate_record(record: StorageRecord): class IndyStorage(BaseStorage): - """Abstract Non-Secrets interface""" + """Indy Non-Secrets interface.""" def __init__(self, wallet: IndyWallet): + """ + Initialize a `BasicStorage` instance. + + Args: + wallet: The indy wallet instance to use + + """ self._wallet = wallet @property def wallet(self) -> IndyWallet: - """Accessor for IndyWallet instance""" + """Accessor for IndyWallet instance.""" return self._wallet async def add_record(self, record: StorageRecord): """ - Add a new record to the store + Add a new record to the store. + + Args: + record: `StorageRecord` to be stored + """ _validate_record(record) tags_json = json.dumps(record.tags) if record.tags else None @@ -46,7 +55,21 @@ async def add_record(self, record: StorageRecord): async def get_record(self, record_type: str, record_id: str) -> StorageRecord: """ - Fetch a record from the store by type and ID + Fetch a record from the store by type and ID. + + Args: + record_type: The record type + record_id: The record id + + Returns: + A `StorageRecord` instance + + Raises: + StorageError: If the record is not provided + StorageError: If the record ID not provided + StorageNotFoundError: If the record is not found + StorageError: If record not found + """ if not record_type: raise StorageError("Record type not provided") @@ -73,7 +96,16 @@ async def get_record(self, record_type: str, record_id: str) -> StorageRecord: async def update_record_value(self, record: StorageRecord, value: str): """ - Update an existing stored record's value + Update an existing stored record's value. + + Args: + record: `StorageRecord` to update + value: The new value + + Raises: + StorageNotFoundError: If record not found + StorageError: If a libindy error occurs + """ _validate_record(record) try: @@ -87,7 +119,16 @@ async def update_record_value(self, record: StorageRecord, value: str): async def update_record_tags(self, record: StorageRecord, tags: Mapping): """ - Update an existing stored record's tags + Update an existing stored record's tags. + + Args: + record: `StorageRecord` to update + tags: New tags + + Raises: + StorageNotFoundError: If record not found + StorageError: If a libindy error occurs + """ _validate_record(record) tags_json = json.dumps(tags) if tags else "{}" @@ -104,7 +145,12 @@ async def delete_record_tags( self, record: StorageRecord, tags: (Sequence, Mapping) ): """ - Update an existing stored record's tags + Update an existing stored record's tags. + + Args: + record: `StorageRecord` to delete + tags: Tags + """ _validate_record(record) if tags: @@ -117,6 +163,17 @@ async def delete_record_tags( ) async def delete_record(self, record: StorageRecord): + """ + Delete a record. + + Args: + record: `StorageRecord` to delete + + Raises: + StorageNotFoundError: If record not found + StorageError: If a libindy error occurs + + """ _validate_record(record) try: await non_secrets.delete_wallet_record( @@ -130,10 +187,24 @@ async def delete_record(self, record: StorageRecord): def search_records( self, type_filter: str, tag_query: Mapping = None, page_size: int = None ) -> "IndyStorageRecordSearch": + """ + Search stored records. + + Args: + type_filter: Filter string + tag_query: Tags to query + page_size: Page size + + Returns: + An instance of `BaseStorageRecordSearch` + + """ return IndyStorageRecordSearch(self, type_filter, tag_query, page_size) class IndyStorageRecordSearch(BaseStorageRecordSearch): + """Represent an active stored records search.""" + def __init__( self, store: IndyStorage, @@ -141,6 +212,16 @@ def __init__( tag_query: Mapping, page_size: int = None, ): + """ + Initialize a `IndyStorageRecordSearch` instance. + + Args: + store: `BaseStorage` to search + type_filter: Filter string + tag_query: Tags to search + page_size: Size of page to return + + """ super(IndyStorageRecordSearch, self).__init__( store, type_filter, tag_query, page_size ) @@ -148,16 +229,39 @@ def __init__( @property def opened(self) -> bool: - """Accessor for open state""" + """ + Accessor for open state. + + Returns: + True if opened, else False + + """ return self._handle is not None @property def handle(self): + """ + Accessor for search handle. + + Returns: + The handle + + """ return self._handle async def fetch(self, max_count: int) -> Sequence[StorageRecord]: """ - Fetch the next list of results from the store + Fetch the next list of results from the store. + + Args: + max_count: Max number of records to return + + Returns: + A list of `StorageRecord`s + + Raises: + StorageSearchError: If the search query has not been opened + """ if not self.opened: raise StorageSearchError("Search query has not been opened") @@ -179,9 +283,7 @@ async def fetch(self, max_count: int) -> Sequence[StorageRecord]: return ret async def open(self): - """ - Start the search query - """ + """Start the search query.""" query_json = json.dumps(self.tag_query or {}) options_json = json.dumps( { @@ -197,9 +299,7 @@ async def open(self): ) async def close(self): - """ - Dispose of the search query - """ + """Dispose of the search query.""" if self._handle: await non_secrets.close_wallet_search(self._handle) self._handle = None diff --git a/agent/indy_catalyst_agent/storage/record.py b/agent/indy_catalyst_agent/storage/record.py index 661b2bb607..ae3b6e18f7 100644 --- a/agent/indy_catalyst_agent/storage/record.py +++ b/agent/indy_catalyst_agent/storage/record.py @@ -1,16 +1,16 @@ -""" -Record instance stored and searchable by BaseStorage implementation -""" +"""Record instance stored and searchable by BaseStorage implementation.""" from collections import namedtuple from uuid import uuid4 class StorageRecord(namedtuple("StorageRecord", "type value tags id")): - """ """ + """Storage record class.""" + __slots__ = () def __new__(cls, type, value, tags: dict = None, id: str = None): + """Initialize some defaults on record.""" if not id: id = uuid4().hex if not tags: diff --git a/agent/indy_catalyst_agent/transport/inbound/base.py b/agent/indy_catalyst_agent/transport/inbound/base.py index 948a8997fc..77b9b1307b 100644 --- a/agent/indy_catalyst_agent/transport/inbound/base.py +++ b/agent/indy_catalyst_agent/transport/inbound/base.py @@ -1,8 +1,19 @@ +"""Base inbound transport class.""" + from abc import ABC, abstractmethod from typing import Callable class BaseInboundTransport(ABC): + """Base inbound transport class.""" + @abstractmethod def start(self, message_router: Callable) -> None: + """ + Start listening for on this transport. + + Args: + message_router: Function to call to route messages + + """ pass diff --git a/agent/indy_catalyst_agent/transport/inbound/http.py b/agent/indy_catalyst_agent/transport/inbound/http.py index 8cb324f7da..1d730663cf 100644 --- a/agent/indy_catalyst_agent/transport/inbound/http.py +++ b/agent/indy_catalyst_agent/transport/inbound/http.py @@ -1,3 +1,5 @@ +"""Http Transport classes and functions.""" + import logging from typing import Callable @@ -9,11 +11,24 @@ class HttpSetupError(BaseError): + """Http setup error.""" + pass class Transport(BaseInboundTransport): + """Http Transport class.""" + def __init__(self, host: str, port: int, message_router: Callable) -> None: + """ + Initialize a Transport instance. + + Args: + host: Host to listen on + port: Port to listen on + message_router: Function to pass incoming messages to + + """ self.host = host self.port = port self.message_router = message_router @@ -23,9 +38,17 @@ def __init__(self, host: str, port: int, message_router: Callable) -> None: @property def scheme(self): + """Accessor for this transport's scheme.""" return self._scheme async def start(self) -> None: + """ + Start this transport. + + Raises: + HttpSetupError: If there was an error starting the webserver + + """ app = web.Application() app.add_routes([web.get("/", self.invite_message_handler)]) app.add_routes([web.post("/", self.inbound_message_handler)]) @@ -41,6 +64,16 @@ async def start(self) -> None: ) async def inbound_message_handler(self, request: web.BaseRequest): + """ + Message handler for inbound messages. + + Args: + request: aiohttp request object + + Returns: + The web response + + """ ctype = request.headers.get("content-type", "") if ctype.split(";", 1)[0].lower() == "application/json": body = await request.text() @@ -58,6 +91,16 @@ async def inbound_message_handler(self, request: web.BaseRequest): return web.Response(status=200) async def invite_message_handler(self, request: web.BaseRequest): + """ + Message handler for invites. + + Args: + request: aiohttp request object + + Returns: + The web response + + """ invite = request.query.get("invite") if invite: invite = b64_to_bytes(invite, urlsafe=True) @@ -66,6 +109,7 @@ async def invite_message_handler(self, request: web.BaseRequest): elif request.query.get("c_i"): return web.Response( text="You have received a connection invitation. To accept the " - "invitation, paste it into your agent application.") + "invitation, paste it into your agent application." + ) else: return web.Response(text="To send an invitation add ?invite=") diff --git a/agent/indy_catalyst_agent/transport/inbound/manager.py b/agent/indy_catalyst_agent/transport/inbound/manager.py index b50d3ec167..79650d228b 100644 --- a/agent/indy_catalyst_agent/transport/inbound/manager.py +++ b/agent/indy_catalyst_agent/transport/inbound/manager.py @@ -1,3 +1,5 @@ +"""Inbound transport manager.""" + import logging from .base import BaseInboundTransport @@ -7,14 +9,26 @@ class InboundTransportManager: + """Inbound transport manager class.""" + def __init__(self): + """Initialize an `InboundTransportManager` instance.""" self.logger = logging.getLogger(__name__) self.class_loader = ClassLoader(MODULE_BASE_PATH, BaseInboundTransport) self.transports = [] def register(self, module_path, host, port, message_handler): - """Register transport module.""" + """ + Register transport module. + + Args: + module_path: Path to module + host: The host to register on + port: The port to register on + message_handler: The message handler for incoming messages + + """ try: imported_class = self.class_loader.load(module_path, True) except (ModuleLoadError, ClassNotFoundError): @@ -24,5 +38,6 @@ def register(self, module_path, host, port, message_handler): self.transports.append(imported_class(host, port, message_handler)) async def start_all(self): + """Start all registered transports.""" for transport in self.transports: await transport.start() diff --git a/agent/indy_catalyst_agent/transport/inbound/ws.py b/agent/indy_catalyst_agent/transport/inbound/ws.py index 5deb2044cf..a9d92d1692 100644 --- a/agent/indy_catalyst_agent/transport/inbound/ws.py +++ b/agent/indy_catalyst_agent/transport/inbound/ws.py @@ -1,3 +1,5 @@ +"""Websockets Transport classes and functions.""" + import logging from typing import Callable @@ -8,11 +10,24 @@ class WsSetupError(BaseError): + """Websocket setup error.""" + pass class Transport(BaseInboundTransport): + """Websockets Transport class.""" + def __init__(self, host: str, port: int, message_router: Callable) -> None: + """ + Initialize a Transport instance. + + Args: + host: Host to listen on + port: Port to listen on + message_router: Function to pass incoming messages to + + """ self.host = host self.port = port self.message_router = message_router @@ -23,9 +38,17 @@ def __init__(self, host: str, port: int, message_router: Callable) -> None: @property def scheme(self): + """Accessor for this transport's scheme.""" return self._scheme async def start(self) -> None: + """ + Start this transport. + + Raises: + HttpSetupError: If there was an error starting the webserver + + """ app = web.Application() app.add_routes([web.get("/", self.inbound_message_handler)]) runner = web.AppRunner(app) @@ -40,6 +63,16 @@ async def start(self) -> None: ) async def inbound_message_handler(self, request): + """ + Message handler for inbound messages. + + Args: + request: aiohttp request object + + Returns: + The web response + + """ ws = web.WebSocketResponse() await ws.prepare(request) diff --git a/agent/indy_catalyst_agent/transport/outbound/base.py b/agent/indy_catalyst_agent/transport/outbound/base.py index 6d340a7b9e..f5b14f1d68 100644 --- a/agent/indy_catalyst_agent/transport/outbound/base.py +++ b/agent/indy_catalyst_agent/transport/outbound/base.py @@ -1,3 +1,5 @@ +"""Base outbound transport.""" + from abc import ABC, abstractmethod, abstractproperty from .message import OutboundMessage @@ -5,26 +7,45 @@ class BaseOutboundTransport(ABC): + """Base outbound transport class.""" + @abstractmethod def __init__(self, queue: BaseOutboundMessageQueue) -> None: + """ + Initialize a `BaseOutboundTransport` instance. + + Args: + queue: `BaseOutboundMessageQueue` to use + + """ pass @abstractmethod async def __aenter__(self): + """Async context manager enter.""" pass @abstractmethod async def __aexit__(self, *err): + """Async context manager exit.""" pass @abstractproperty def queue(self): + """Accessor for queue.""" pass @abstractmethod async def handle_message(self, message: OutboundMessage): + """ + Handle message from queue. + + Args: + message: `OutboundMessage` to send over transport implementation + """ pass async def start(self) -> None: + """Start this transport.""" async for message in self.queue: await self.handle_message(message) diff --git a/agent/indy_catalyst_agent/transport/outbound/error.py b/agent/indy_catalyst_agent/transport/outbound/error.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/agent/indy_catalyst_agent/transport/outbound/http.py b/agent/indy_catalyst_agent/transport/outbound/http.py index 071317709c..18214b69e6 100644 --- a/agent/indy_catalyst_agent/transport/outbound/http.py +++ b/agent/indy_catalyst_agent/transport/outbound/http.py @@ -1,3 +1,5 @@ +"""Http outbound transport.""" + import logging from aiohttp import ClientSession @@ -8,27 +10,38 @@ class HttpTransport(BaseOutboundTransport): + """Http outbound transport class.""" schemes = ("http", "https") def __init__(self, queue: BaseOutboundMessageQueue) -> None: + """Initialize an `HttpTransport` instance.""" self.logger = logging.getLogger(__name__) self._queue = queue async def __aenter__(self): + """Async context manager enter.""" self.client_session = ClientSession() return self async def __aexit__(self, *err): + """Async context manager exit.""" await self.client_session.close() self.client_session = None self.logger.error(err) @property def queue(self): + """Accessor for queue.""" return self._queue async def handle_message(self, message: OutboundMessage): + """ + Handle message from queue. + + Args: + message: `OutboundMessage` to send over transport implementation + """ try: headers = {} if isinstance(message.data, bytes): @@ -36,7 +49,7 @@ async def handle_message(self, message: OutboundMessage): else: headers["Content-Type"] = "application/json" async with self.client_session.post( - message.uri, data=message.data, headers=headers, + message.uri, data=message.data, headers=headers ) as response: self.logger.info(response.status) except Exception: diff --git a/agent/indy_catalyst_agent/transport/outbound/manager.py b/agent/indy_catalyst_agent/transport/outbound/manager.py index 8cfa2ae1d1..c5c29a9474 100644 --- a/agent/indy_catalyst_agent/transport/outbound/manager.py +++ b/agent/indy_catalyst_agent/transport/outbound/manager.py @@ -1,3 +1,5 @@ +"""Outbound transport manager.""" + import asyncio import logging @@ -15,15 +17,22 @@ class OutboundTransportRegistrationError(BaseError): - """ """ + """Outbound transport registration error.""" pass class OutboundTransportManager: - """ """ + """Outbound transport manager class.""" def __init__(self, queue: Type[BaseOutboundMessageQueue]): + """ + Initialize a `OutboundTransportManager` instance. + + Args: + queue: `BaseOutboundMessageQueue` implementation to use + + """ self.logger = logging.getLogger(__name__) self.registered_transports = {} self.running_transports = {} @@ -31,6 +40,19 @@ def __init__(self, queue: Type[BaseOutboundMessageQueue]): self.queue = queue def register(self, module_path): + """ + Register a new outbound transport. + + Args: + module_path: Module path to register + + Raises: + OutboundTransportRegistrationError: If the imported class does not + specify a schemes attribute + OutboundTransportRegistrationError: If the scheme has already been + registered + + """ imported_class = self.class_loader.load(module_path, True) try: @@ -54,18 +76,31 @@ def register(self, module_path): self.registered_transports[schemes] = imported_class async def start(self, schemes, transport): + """Start the transport.""" # All transports share the same queue async with transport(self.queue()) as t: self.running_transports[schemes] = t await t.start() async def start_all(self): + """Start all transports.""" for schemes, transport_class in self.registered_transports.items(): # Don't block the loop # asyncio.create_task(self.start(schemes, transport_class)) asyncio.ensure_future(self.start(schemes, transport_class)) async def send_message(self, message, uri: str): + """ + Send a message. + + Find a registered transport for the scheme in the uri and + use it to send the message. + + Args: + message: The agent message to send + uri: Where are you sending the message + + """ # Grab the scheme from the uri scheme = urlparse(uri).scheme if scheme == "": diff --git a/agent/indy_catalyst_agent/transport/outbound/message.py b/agent/indy_catalyst_agent/transport/outbound/message.py index 4cdb0b90b8..78ed39c1c2 100644 --- a/agent/indy_catalyst_agent/transport/outbound/message.py +++ b/agent/indy_catalyst_agent/transport/outbound/message.py @@ -1,3 +1,5 @@ +"""Data shape for outbound message.""" + from collections import namedtuple OutboundMessage = namedtuple("OutboundMessage", "data uri") diff --git a/agent/indy_catalyst_agent/transport/outbound/queue/base.py b/agent/indy_catalyst_agent/transport/outbound/queue/base.py index 04a06f9801..43c8aa2051 100644 --- a/agent/indy_catalyst_agent/transport/outbound/queue/base.py +++ b/agent/indy_catalyst_agent/transport/outbound/queue/base.py @@ -1,20 +1,32 @@ +"""Abstract outbound queue.""" + from abc import ABC, abstractmethod class BaseOutboundMessageQueue(ABC): - """ """ + """Abstract outbound queue class.""" + @abstractmethod async def enqueue(self, message): + """ + Enqueue a message. + + Args: + message: The message to send + """ pass @abstractmethod async def dequeue(self): + """Get a message off the queue.""" pass @abstractmethod def __aiter__(self): + """Async iterator magic method.""" pass @abstractmethod async def __anext__(self): + """Async iterator magic method.""" pass diff --git a/agent/indy_catalyst_agent/transport/outbound/queue/basic.py b/agent/indy_catalyst_agent/transport/outbound/queue/basic.py index 58e724a132..95e9e5d303 100644 --- a/agent/indy_catalyst_agent/transport/outbound/queue/basic.py +++ b/agent/indy_catalyst_agent/transport/outbound/queue/basic.py @@ -1,3 +1,5 @@ +"""Basic in memory queue.""" + import logging from asyncio import Queue @@ -6,23 +8,41 @@ class BasicOutboundMessageQueue(BaseOutboundMessageQueue): - """ """ + """Basic in memory queue implementation class.""" + def __init__(self): + """Initialize a `BasicOutboundMessageQueue` instance.""" self.queue = Queue() self.logger = logging.getLogger(__name__) async def enqueue(self, message): + """ + Enqueue a message. + + Args: + message: The message to send + + """ self.logger.debug(f"Enqueuing message: {message}") await self.queue.put(message) async def dequeue(self): + """ + Dequeue a message. + + Returns: + The dequeued message + + """ message = await self.queue.get() self.logger.debug(f"Dequeuing message: {message}") return message def __aiter__(self): + """Async iterator magic method.""" return self async def __anext__(self): + """Async iterator magic method.""" message = await self.dequeue() return message diff --git a/agent/indy_catalyst_agent/transport/outbound/ws.py b/agent/indy_catalyst_agent/transport/outbound/ws.py index 8e9f778791..7f115ebef2 100644 --- a/agent/indy_catalyst_agent/transport/outbound/ws.py +++ b/agent/indy_catalyst_agent/transport/outbound/ws.py @@ -1,7 +1,4 @@ -""" -Connects to another agent as a websocket client. -""" - +"""Websockets outbound transport.""" import logging @@ -13,27 +10,38 @@ class WsTransport(BaseOutboundTransport): + """Websockets outbound transport class.""" schemes = ("ws", "wss") def __init__(self, queue: BaseOutboundMessageQueue) -> None: + """Initialize an `HttpTransport` instance.""" self.logger = logging.getLogger(__name__) self._queue = queue async def __aenter__(self): + """Async context manager enter.""" self.client_session = ClientSession() return self async def __aexit__(self, *err): + """Async context manager exit.""" await self.client_session.close() self.client_session = None self.logger.error(err) @property def queue(self): + """Accessor for queue.""" return self._queue async def handle_message(self, message: OutboundMessage): + """ + Handle message from queue. + + Args: + message: `OutboundMessage` to send over transport implementation + """ try: # As an example, we can open a websocket channel, send a message, then # close the channel immediately. This is not optimal but it works. diff --git a/agent/indy_catalyst_agent/wallet/base.py b/agent/indy_catalyst_agent/wallet/base.py index ca8ae4a7a3..d3f88f4b77 100644 --- a/agent/indy_catalyst_agent/wallet/base.py +++ b/agent/indy_catalyst_agent/wallet/base.py @@ -240,6 +240,7 @@ async def sign_message(self, message: bytes, from_verkey: str) -> bytes: Returns: The signature + """ @abstractmethod diff --git a/agent/setup.cfg b/agent/setup.cfg index 3361595974..f1dd6ac854 100644 --- a/agent/setup.cfg +++ b/agent/setup.cfg @@ -7,4 +7,5 @@ addopts = --cov-config .coveragerc --cov=indy_catalyst_agent --cov-report term - max-line-length = 88 exclude = */tests/** + */__init__.py ignore = D202, W503 \ No newline at end of file