diff --git a/aries_cloudagent/admin/server.py b/aries_cloudagent/admin/server.py index 9230536808..4497a86ac1 100644 --- a/aries_cloudagent/admin/server.py +++ b/aries_cloudagent/admin/server.py @@ -2,9 +2,14 @@ import asyncio import logging -from typing import Callable, Coroutine, Sequence, Set +import re import uuid +from typing import Callable, Coroutine, Sequence, Set + +import aiohttp_cors +import jwt + from aiohttp import web from aiohttp_apispec import ( docs, @@ -12,8 +17,6 @@ setup_aiohttp_apispec, validation_middleware, ) -import aiohttp_cors -import jwt from marshmallow import fields @@ -364,12 +367,20 @@ async def setup_context(request: web.Request, handler): middlewares.append(setup_context) - app = web.Application(middlewares=middlewares) + app = web.Application( + middlewares=middlewares, + client_max_size=( + self.context.settings.get("admin.admin_client_max_request_size", 1) + * 1024 + * 1024 + ), + ) server_routes = [ web.get("/", self.redirect_handler, allow_head=False), web.get("/plugins", self.plugins_handler, allow_head=False), web.get("/status", self.status_handler, allow_head=False), + web.get("/status/config", self.config_handler, allow_head=False), web.post("/status/reset", self.status_reset_handler), web.get("/status/live", self.liveliness_handler, allow_head=False), web.get("/status/ready", self.readiness_handler, allow_head=False), @@ -523,6 +534,41 @@ async def plugins_handler(self, request: web.BaseRequest): plugins = registry and sorted(registry.plugin_names) or [] return web.json_response({"result": plugins}) + @docs(tags=["server"], summary="Fetch the server configuration") + @response_schema(AdminStatusSchema(), 200, description="") + async def config_handler(self, request: web.BaseRequest): + """ + Request handler for the server configuration. + + Args: + request: aiohttp request object + + Returns: + The web response + + """ + config = { + k: self.context.settings[k] + for k in self.context.settings + if k + not in [ + "admin.admin_api_key", + "multitenant.jwt_secret", + "wallet.key", + "wallet.rekey", + "wallet.seed", + "wallet.storage.creds", + ] + } + for index in range(len(config.get("admin.webhook_urls", []))): + config["admin.webhook_urls"][index] = re.sub( + r"#.*", + "", + config["admin.webhook_urls"][index], + ) + + return web.json_response(config) + @docs(tags=["server"], summary="Fetch the server status") @response_schema(AdminStatusSchema(), 200, description="") async def status_handler(self, request: web.BaseRequest): diff --git a/aries_cloudagent/admin/tests/test_admin_server.py b/aries_cloudagent/admin/tests/test_admin_server.py index d6137f917f..52c3cbf94c 100644 --- a/aries_cloudagent/admin/tests/test_admin_server.py +++ b/aries_cloudagent/admin/tests/test_admin_server.py @@ -1,3 +1,5 @@ +import json + from aiohttp import ClientSession, DummyCookieJar, TCPConnector, web from aiohttp.test_utils import unused_port @@ -190,10 +192,12 @@ async def test_start_stop(self): settings = { "admin.admin_insecure_mode": False, + "admin.admin_client_max_request_size": 4, "admin.admin_api_key": "test-api-key", } server = self.get_admin_server(settings) await server.start() + assert server.app._client_max_size == 4 * 1024 * 1024 with async_mock.patch.object( server, "websocket_queues", async_mock.MagicMock() ) as mock_wsq: @@ -372,6 +376,42 @@ async def test_visit_secure_mode(self): await server.stop() + async def test_query_config(self): + settings = { + "admin.admin_insecure_mode": False, + "admin.admin_api_key": "test-api-key", + "admin.webhook_urls": ["localhost:8123/abc#secret", "localhost:8123/def"], + "multitenant.jwt_secret": "abc123", + "wallet.key": "abc123", + "wallet.rekey": "def456", + "wallet.seed": "00000000000000000000000000000000", + "wallet.storage.creds": "secret", + } + server = self.get_admin_server(settings) + await server.start() + + async with self.client_session.get( + f"http://127.0.0.1:{self.port}/status/config", + headers={"x-api-key": "test-api-key"}, + ) as response: + result = json.loads(await response.text()) + assert "admin.admin_insecure_mode" in result + assert all( + k not in result + for k in [ + "admin.admin_api_key", + "multitenant.jwt_secret", + "wallet.key", + "wallet.rekey", + "wallet.seed", + "wallet.storage.creds", + ] + ) + assert result["admin.webhook_urls"] == [ + "localhost:8123/abc", + "localhost:8123/def", + ] + async def test_visit_shutting_down(self): settings = { "admin.admin_insecure_mode": True, diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index 900ec6c6ac..ae85ab0bb6 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -99,54 +99,73 @@ def add_arguments(self, parser: ArgumentParser): nargs=2, metavar=("", ""), env_var="ACAPY_ADMIN", - help="Specify the host and port on which to run the administrative server.\ - If not provided, no admin server is made available.", + help=( + "Specify the host and port on which to run the administrative server. " + "If not provided, no admin server is made available." + ), ) parser.add_argument( "--admin-api-key", type=str, metavar="", env_var="ACAPY_ADMIN_API_KEY", - help="Protect all admin endpoints with the provided API key.\ - API clients (e.g. the controller) must pass the key in the HTTP\ - header using 'X-API-Key: '. Either this parameter or the\ - '--admin-insecure-mode' parameter MUST be specified.", + help=( + "Protect all admin endpoints with the provided API key. " + "API clients (e.g. the controller) must pass the key in the HTTP " + "header using 'X-API-Key: '. Either this parameter or the " + "'--admin-insecure-mode' parameter MUST be specified." + ), ) parser.add_argument( "--admin-insecure-mode", action="store_true", env_var="ACAPY_ADMIN_INSECURE_MODE", - help="Run the admin web server in insecure mode. DO NOT USE FOR\ - PRODUCTION DEPLOYMENTS. The admin server will be publicly available\ - to anyone who has access to the interface. Either this parameter or\ - the '--api-key' parameter MUST be specified.", + help=( + "Run the admin web server in insecure mode. DO NOT USE FOR " + "PRODUCTION DEPLOYMENTS. The admin server will be publicly available " + "to anyone who has access to the interface. Either this parameter or " + "the '--api-key' parameter MUST be specified." + ), ) parser.add_argument( "--no-receive-invites", action="store_true", env_var="ACAPY_NO_RECEIVE_INVITES", - help="Prevents an agent from receiving invites by removing the\ - '/connections/receive-invite' route from the administrative\ - interface. Default: false.", + help=( + "Prevents an agent from receiving invites by removing the " + "'/connections/receive-invite' route from the administrative " + "interface. Default: false." + ), ) parser.add_argument( "--help-link", type=str, metavar="", env_var="ACAPY_HELP_LINK", - help="A URL to an administrative interface help web page that a controller\ - user interface can get from the agent and provide as a link to users.", + help=( + "A URL to an administrative interface help web page that a controller " + "user interface can get from the agent and provide as a link to users." + ), ) parser.add_argument( "--webhook-url", action="append", metavar="", env_var="ACAPY_WEBHOOK_URL", - help="Send webhooks containing internal state changes to the specified\ - URL. Optional API key to be passed in the request body can be appended using\ - a hash separator [#]. This is useful for a controller to monitor agent events\ - and respond to those events using the admin API. If not specified, \ - webhooks are not published by the agent.", + help=( + "Send webhooks containing internal state changes to the specified " + "URL. Optional API key to be passed in the request body can be " + "appended using a hash separator [#]. This is useful for a controller " + "to monitor agent events and respond to those events using the " + "admin API. If not specified, webhooks are not published by the agent." + ), + ) + parser.add_argument( + "--admin-client-max-request-size", + default=1, + type=BoundedInt(min=1, max=16), + env_var="ACAPY_ADMIN_CLIENT_MAX_REQUEST_SIZE", + help="Maximum client request size to admin server, in megabytes: default 1", ) def get_settings(self, args: Namespace): @@ -161,7 +180,7 @@ def get_settings(self, args: Namespace): ): raise ArgsParseError( "Either --admin-api-key or --admin-insecure-mode " - + "must be set but not both." + "must be set but not both." ) settings["admin.admin_api_key"] = admin_api_key @@ -179,6 +198,10 @@ def get_settings(self, args: Namespace): if hook_url: hook_urls.append(hook_url) settings["admin.webhook_urls"] = hook_urls + + settings["admin.admin_client_max_request_size"] = ( + args.admin_client_max_request_size or 1 + ) return settings @@ -194,9 +217,11 @@ def add_arguments(self, parser: ArgumentParser): "--debug", action="store_true", env_var="ACAPY_DEBUG", - help="Enables a remote debugging service that can be accessed\ - using ptvsd for Visual Studio Code. The framework will wait\ - for the debugger to connect at start-up. Default: false.", + help=( + "Enables a remote debugging service that can be accessed " + "using ptvsd for Visual Studio Code. The framework will wait " + "for the debugger to connect at start-up. Default: false." + ), ) parser.add_argument( "--debug-seed", @@ -216,29 +241,37 @@ def add_arguments(self, parser: ArgumentParser): "--debug-credentials", action="store_true", env_var="ACAPY_DEBUG_CREDENTIALS", - help="Enable additional logging around credential exchanges.\ - Default: false.", + help=( + "Enable additional logging around credential exchanges. " + "Default: false." + ), ) parser.add_argument( "--debug-presentations", action="store_true", env_var="ACAPY_DEBUG_PRESENTATIONS", - help="Enable additional logging around presentation exchanges.\ - Default: false.", + help=( + "Enable additional logging around presentation exchanges. " + "Default: false." + ), ) parser.add_argument( "--invite", action="store_true", env_var="ACAPY_INVITE", - help="After startup, generate and print a new out-of-band connection invitation\ - URL. Default: false.", + help=( + "After startup, generate and print a new out-of-band connection " + "invitation URL. Default: false." + ), ) parser.add_argument( "--connections-invite", action="store_true", env_var="ACAPY_CONNECTIONS_INVITE", - help="After startup, generate and print a new connections protocol \ - style invitation URL. Default: false.", + help=( + "After startup, generate and print a new connections protocol " + "style invitation URL. Default: false." + ), ) parser.add_argument( "--invite-label", @@ -279,36 +312,46 @@ def add_arguments(self, parser: ArgumentParser): "--auto-accept-invites", action="store_true", env_var="ACAPY_AUTO_ACCEPT_INVITES", - help="Automatically accept invites without firing a webhook event or\ - waiting for an admin request. Default: false.", + help=( + "Automatically accept invites without firing a webhook event or " + "waiting for an admin request. Default: false." + ), ) parser.add_argument( "--auto-accept-requests", action="store_true", env_var="ACAPY_AUTO_ACCEPT_REQUESTS", - help="Automatically accept connection requests without firing\ - a webhook event or waiting for an admin request. Default: false.", + help=( + "Automatically accept connection requests without firing " + "a webhook event or waiting for an admin request. Default: false." + ), ) parser.add_argument( "--auto-respond-messages", action="store_true", env_var="ACAPY_AUTO_RESPOND_MESSAGES", - help="Automatically respond to basic messages indicating the message was\ - received. Default: false.", + help=( + "Automatically respond to basic messages indicating the message was " + "received. Default: false." + ), ) parser.add_argument( "--auto-respond-credential-proposal", action="store_true", env_var="ACAPY_AUTO_RESPOND_CREDENTIAL_PROPOSAL", - help="Auto-respond to credential proposals with corresponding " - + "credential offers", + help=( + "Auto-respond to credential proposals with corresponding " + "credential offers" + ), ) parser.add_argument( "--auto-respond-credential-offer", action="store_true", env_var="ACAPY_AUTO_RESPOND_CREDENTIAL_OFFER", - help="Automatically respond to Indy credential offers with a credential\ - request. Default: false", + help=( + "Automatically respond to Indy credential offers with a credential " + "request. Default: false" + ), ) parser.add_argument( "--auto-respond-credential-request", @@ -320,30 +363,39 @@ def add_arguments(self, parser: ArgumentParser): "--auto-respond-presentation-proposal", action="store_true", env_var="ACAPY_AUTO_RESPOND_PRESENTATION_PROPOSAL", - help="Auto-respond to presentation proposals with corresponding " - + "presentation requests", + help=( + "Auto-respond to presentation proposals with corresponding " + "presentation requests" + ), ) parser.add_argument( "--auto-respond-presentation-request", action="store_true", env_var="ACAPY_AUTO_RESPOND_PRESENTATION_REQUEST", - help="Automatically respond to Indy presentation requests with a\ - constructed presentation if a corresponding credential can be retrieved\ - for every referent in the presentation request. Default: false.", + help=( + "Automatically respond to Indy presentation requests with a " + "constructed presentation if a corresponding credential can be " + "retrieved for every referent in the presentation request. " + "Default: false." + ), ) parser.add_argument( "--auto-store-credential", action="store_true", env_var="ACAPY_AUTO_STORE_CREDENTIAL", - help="Automatically store an issued credential upon receipt.\ - Default: false.", + help=( + "Automatically store an issued credential upon receipt. " + "Default: false." + ), ) parser.add_argument( "--auto-verify-presentation", action="store_true", env_var="ACAPY_AUTO_VERIFY_PRESENTATION", - help="Automatically verify a presentation when it is received.\ - Default: false.", + help=( + "Automatically verify a presentation when it is received. " + "Default: false." + ), ) def get_settings(self, args: Namespace) -> dict: @@ -408,8 +460,10 @@ def add_arguments(self, parser: ArgumentParser): parser.add_argument( "--arg-file", is_config_file=True, - help="Load aca-py arguments from the specified file. Note that\ - this file *must* be in YAML format.", + help=( + "Load aca-py arguments from the specified file. Note that " + "this file *must* be in YAML format." + ), ) parser.add_argument( "--plugin", @@ -419,19 +473,23 @@ def add_arguments(self, parser: ArgumentParser): required=False, metavar="", env_var="ACAPY_PLUGIN", - help="Load as external plugin module. Multiple\ - instances of this parameter can be specified.", + help=( + "Load as external plugin module. Multiple " + "instances of this parameter can be specified." + ), ) parser.add_argument( "--storage-type", type=str, metavar="", env_var="ACAPY_STORAGE_TYPE", - help="Specifies the type of storage provider to use for the internal\ - storage engine. This storage interface is used to store internal state.\ - Supported internal storage types are 'basic' (memory)\ - and 'indy'. The default (if not specified) is 'indy' if the wallet type\ - is set to 'indy', otherwise 'basic'.", + help=( + "Specifies the type of storage provider to use for the internal " + "storage engine. This storage interface is used to store internal " + "state Supported internal storage types are 'basic' (memory) " + "and 'indy'. The default (if not specified) is 'indy' if the " + "wallet type is set to 'indy', otherwise 'basic'." + ), ) parser.add_argument( "-e", @@ -440,15 +498,17 @@ def add_arguments(self, parser: ArgumentParser): nargs="+", metavar="", env_var="ACAPY_ENDPOINT", - help="Specifies the endpoints to put into DIDDocs\ - to inform other agents of where they should send messages destined\ - for this agent. Each endpoint could be one of the specified inbound\ - transports for this agent, or the endpoint could be that of\ - another agent (e.g. 'https://example.com/agent-endpoint') if the\ - routing of messages to this agent by a mediator is configured.\ - The first endpoint specified will be used in invitations.\ - The endpoints are used in the formation of a connection\ - with another agent.", + help=( + "Specifies the endpoints to put into DIDDocs " + "to inform other agents of where they should send messages destined " + "for this agent. Each endpoint could be one of the specified inbound " + "transports for this agent, or the endpoint could be that of " + "another agent (e.g. 'https://example.com/agent-endpoint') if the " + "routing of messages to this agent by a mediator is configured. " + "The first endpoint specified will be used in invitations. " + "The endpoints are used in the formation of a connection " + "with another agent." + ), ) parser.add_argument( "--profile-endpoint", @@ -461,8 +521,7 @@ def add_arguments(self, parser: ArgumentParser): "--read-only-ledger", action="store_true", env_var="ACAPY_READ_ONLY_LEDGER", - help="Sets ledger to read-only to prevent updates.\ - Default: false.", + help="Sets ledger to read-only to prevent updates. Default: false.", ) parser.add_argument( "--tails-server-base-url", @@ -476,8 +535,10 @@ def add_arguments(self, parser: ArgumentParser): type=str, metavar="", env_var="ACAPY_TAILS_SERVER_UPLOAD_URL", - help="Sets the base url of the tails server for upload, defaulting to the\ - tails server base url.", + help=( + "Sets the base url of the tails server for upload, defaulting to the " + "tails server base url." + ), ) def get_settings(self, args: Namespace) -> dict: @@ -519,8 +580,10 @@ def add_arguments(self, parser: ArgumentParser): type=str, metavar="", env_var="ACAPY_LEDGER_POOL_NAME", - help="Specifies the name of the indy pool to be opened.\ - This is useful if you have multiple pool configurations.", + help=( + "Specifies the name of the indy pool to be opened. " + "This is useful if you have multiple pool configurations." + ), ) parser.add_argument( "--genesis-transactions", @@ -528,9 +591,11 @@ def add_arguments(self, parser: ArgumentParser): dest="genesis_transactions", metavar="", env_var="ACAPY_GENESIS_TRANSACTIONS", - help='Specifies the genesis transactions to use to connect to\ - an Hyperledger Indy ledger. The transactions are provided as string\ - of JSON e.g. \'{"reqSignature":{},"txn":{"data":{"d... \'', + help=( + "Specifies the genesis transactions to use to connect to " + "a Hyperledger Indy ledger. The transactions are provided as string " + 'of JSON e.g. \'{"reqSignature":{},"txn":{"data":{"d... }}}\'' + ), ) parser.add_argument( "--genesis-file", @@ -546,18 +611,23 @@ def add_arguments(self, parser: ArgumentParser): dest="genesis_url", metavar="", env_var="ACAPY_GENESIS_URL", - help="Specifies the url from which to download the genesis\ - transactions. For example, if you are using 'von-network',\ - the URL might be 'http://localhost:9000/genesis'.\ - Genesis transactions URLs are available for the Sovrin test/main networks.", + help=( + "Specifies the url from which to download the genesis " + "transactions. For example, if you are using 'von-network', " + "the URL might be 'http://localhost:9000/genesis'. " + "Genesis transactions URLs are available for the " + "Sovrin test/main networks." + ), ) parser.add_argument( "--no-ledger", action="store_true", env_var="ACAPY_NO_LEDGER", - help="Specifies that aca-py will run with no ledger configured.\ - This must be set if running in no-ledger mode. Overrides any\ - specified ledger or genesis configurations. Default: false.", + help=( + "Specifies that aca-py will run with no ledger configured. " + "This must be set if running in no-ledger mode. Overrides any " + "specified ledger or genesis configurations. Default: false." + ), ) parser.add_argument( "--ledger-keepalive", @@ -582,8 +652,8 @@ def get_settings(self, args: Namespace) -> dict: else: raise ArgsParseError( "One of --genesis-url --genesis-file or --genesis-transactions " - + "must be specified (unless --no-ledger is specified to " - + "explicitly configure aca-py to run with no ledger)." + "must be specified (unless --no-ledger is specified to " + "explicitly configure aca-py to run with no ledger)." ) if args.ledger_pool_name: settings["ledger.pool_name"] = args.ledger_pool_name @@ -617,8 +687,10 @@ def add_arguments(self, parser: ArgumentParser): metavar="", default=None, env_var="ACAPY_LOG_FILE", - help="Overrides the output destination for the root logger (as defined\ - by the log config file) to the named .", + help=( + "Overrides the output destination for the root logger (as defined " + "by the log config file) to the named ." + ), ) parser.add_argument( "--log-level", @@ -627,8 +699,10 @@ def add_arguments(self, parser: ArgumentParser): metavar="", default=None, env_var="ACAPY_LOG_LEVEL", - help="Specifies a custom logging level as one of:\ - ('debug', 'info', 'warning', 'error', 'critical')", + help=( + "Specifies a custom logging level as one of: " + "('debug', 'info', 'warning', 'error', 'critical')" + ), ) def get_settings(self, args: Namespace) -> dict: @@ -655,16 +729,20 @@ def add_arguments(self, parser: ArgumentParser): "--auto-ping-connection", action="store_true", env_var="ACAPY_AUTO_PING_CONNECTION", - help="Automatically send a trust ping immediately after a\ - connection response is accepted. Some agents require this before\ - marking a connection as 'active'. Default: false.", + help=( + "Automatically send a trust ping immediately after a " + "connection response is accepted. Some agents require this before " + "marking a connection as 'active'. Default: false." + ), ) parser.add_argument( "--invite-base-url", type=str, metavar="", env_var="ACAPY_INVITE_BASE_URL", - help="Base URL to use when formatting connection invitations in URL format.", + help=( + "Base URL to use when formatting connection invitations in URL format." + ), ) parser.add_argument( "--monitor-ping", @@ -676,8 +754,10 @@ def add_arguments(self, parser: ArgumentParser): "--public-invites", action="store_true", env_var="ACAPY_PUBLIC_INVITES", - help="Send invitations out, and receive connection requests,\ - using the public DID for the agent. Default: false.", + help=( + "Send invitations out, and receive connection requests, " + "using the public DID for the agent. Default: false." + ), ) parser.add_argument( "--timing", @@ -729,24 +809,30 @@ def add_arguments(self, parser: ArgumentParser): "--emit-new-didcomm-prefix", action="store_true", env_var="ACAPY_EMIT_NEW_DIDCOMM_PREFIX", - help="Emit protocol messages with new DIDComm prefix; i.e.,\ - 'https://didcomm.org/' instead of (default) prefix\ - 'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'.", + help=( + "Emit protocol messages with new DIDComm prefix; i.e., " + "'https://didcomm.org/' instead of (default) prefix " + "'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'." + ), ) parser.add_argument( "--emit-new-didcomm-mime-type", action="store_true", env_var="ACAPY_EMIT_NEW_DIDCOMM_MIME_TYPE", - help="Send packed agent messages with the DIDComm MIME type\ - as of RFC 0044; i.e., 'application/didcomm-envelope-enc'\ - instead of 'application/ssi-agent-wire'.", + help=( + "Send packed agent messages with the DIDComm MIME type " + "as of RFC 0044; i.e., 'application/didcomm-envelope-enc' " + "instead of 'application/ssi-agent-wire'." + ), ) parser.add_argument( "--exch-use-unencrypted-tags", action="store_true", env_var="ACAPY_EXCH_USE_UNENCRYPTED_TAGS", - help="Store tags for exchange protocols (credential and presentation)\ - using unencrypted rather than encrypted tags", + help=( + "Store tags for exchange protocols (credential and presentation) " + "using unencrypted rather than encrypted tags" + ), ) def get_settings(self, args: Namespace) -> dict: @@ -818,8 +904,10 @@ def add_arguments(self, parser: ArgumentParser): "--auto-provision", action="store_true", env_var="ACAPY_AUTO_PROVISION", - help="If the requested profile does not exist, initialize it with\ - the given parameters.", + help=( + "If the requested profile does not exist, initialize it with " + "the given parameters." + ), ) def get_settings(self, args: Namespace): @@ -847,12 +935,14 @@ def add_arguments(self, parser: ArgumentParser): nargs=3, metavar=("", "", ""), env_var="ACAPY_INBOUND_TRANSPORT", - help="REQUIRED. Defines the inbound transport(s) on which the agent\ - listens for receiving messages from other agents. This parameter can\ - be specified multiple times to create multiple interfaces.\ - Built-in inbound transport types include 'http' and 'ws'.\ - However, other transports can be loaded by specifying an absolute\ - module path.", + help=( + "REQUIRED. Defines the inbound transport(s) on which the agent " + "listens for receiving messages from other agents. This parameter can " + "be specified multiple times to create multiple interfaces. " + "Built-in inbound transport types include 'http' and 'ws'. " + "However, other transports can be loaded by specifying an absolute " + "module path." + ), ) parser.add_argument( "-ot", @@ -862,10 +952,12 @@ def add_arguments(self, parser: ArgumentParser): action="append", metavar="", env_var="ACAPY_OUTBOUND_TRANSPORT", - help="REQUIRED. Defines the outbound transport(s) on which the agent\ - will send outgoing messages to other agents. This parameter can be passed\ - multiple times to supoort multiple transport types. Supported outbound\ - transport types are 'http' and 'ws'.", + help=( + "REQUIRED. Defines the outbound transport(s) on which the agent " + "will send outgoing messages to other agents. This parameter can be " + "passed multiple times to supoort multiple transport types. " + "Supported outbound transport types are 'http' and 'ws'." + ), ) parser.add_argument( "-oq", @@ -873,9 +965,10 @@ def add_arguments(self, parser: ArgumentParser): dest="outbound_queue", type=str, env_var="ACAPY_OUTBOUND_TRANSPORT_QUEUE", - help="""Defines connection details for outbound queue in a single - connection string. For example, 'redis://127.0.0.1:6379'. - """, + help=( + "Defines connection details for outbound queue in a single " + "connection string; e.g., 'redis://127.0.0.1:6379'." + ), ) parser.add_argument( "-oqp", @@ -883,10 +976,11 @@ def add_arguments(self, parser: ArgumentParser): dest="outbound_queue_prefix", type=str, env_var="ACAPY_OUTBOUND_TRANSPORT_QUEUE_PREFIX", - help="""Defines the prefix used to generate the queue key. The - default is 'acapy', which generates a queue key as follows: - 'acapy.outbound_transport'. - """, + help=( + "Defines the prefix used to generate the queue key. The " + "default is 'acapy', which generates a queue key as follows: " + "'acapy.outbound_transport'." + ), ) parser.add_argument( "-oqc", @@ -894,13 +988,15 @@ def add_arguments(self, parser: ArgumentParser): dest="outbound_queue_class", type=str, env_var="ACAPY_OUTBOUND_TRANSPORT_QUEUE_CLASS", - help="""Defines the location of the Outbound Queue Engine. This must be - a 'dotpath' to a Python module on the PYTHONPATH, followed by a - colon, followed by the name of a Python class that implements - BaseOutboundQueue. This commandline option is the official entry - point of ACA-py's pluggable queue interface. The default value is: - 'aries_cloudagent.transport.outbound.queue.redis:RedisOutboundQueue' - """, + help=( + "Defines the location of the Outbound Queue Engine. This must be " + "a 'dotpath' to a Python module on the PYTHONPATH, followed by a " + "colon, followed by the name of a Python class that implements " + "BaseOutboundQueue. This commandline option is the official entry " + "point of ACA-py's pluggable queue interface. The default value is: " + "'aries_cloudagent.transport.outbound.queue.redis:RedisOutboundQueue'." + "" + ), ) parser.add_argument( "-l", @@ -908,15 +1004,19 @@ def add_arguments(self, parser: ArgumentParser): type=str, metavar="