Skip to content

Commit 0c67769

Browse files
committed
Refactor agent controller
* Use dataclass to make it more comprehensive * Improve header and token handling ** tennant_jwt and api_key are now class attributes ** updates now attr and header ** can be removed * remove duplicate session code * Add docstrings
1 parent 7ebed18 commit 0c67769

File tree

1 file changed

+179
-80
lines changed

1 file changed

+179
-80
lines changed

libs/aries-basic-controller/aries_basic_controller/aries_controller.py

Lines changed: 179 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
ClientSession,
44
ClientRequest,
55
)
6+
from dataclasses import dataclass
67
from pubsub import pub
78

89
from .controllers.connections import ConnectionsController
@@ -25,51 +26,84 @@
2526

2627
logger = logging.getLogger("aries_controller")
2728

28-
class AriesAgentController:
2929

30+
@dataclass
31+
class AriesAgentController:
32+
"""The Aries Agent Controller class
33+
34+
This class allows you to interact with Aries by exposing the aca-py API.
35+
36+
Attributes
37+
----------
38+
webhook_host : str
39+
The url of the webhook host
40+
webhook_port : int
41+
The exposed port for webhooks on the host
42+
admin_url : str
43+
The URL for the Admin API
44+
webhook_base : str
45+
The base url for webhooks (default is "")
46+
connections : bool
47+
Specify whether to create connecitons (default is True)
48+
messaging : bool
49+
Initialise the messaging interface (default is True)
50+
multitenant : bool
51+
Initialise the multitenant interface (default is False)
52+
mediation : bool
53+
Initialise the mediation interface (default is False)
54+
issuer : bool
55+
Initialise the issuer interface (defautl is True)
56+
action_menu : bool
57+
Initialise the action menu interface (default is True)
58+
revocations : bool
59+
Initialise revocation interface for credentials (default is True)
60+
api_key : str
61+
The API key (default is None)
62+
tennant_jwt: str
63+
The tennant JW token (default is None)
64+
"""
65+
3066
## TODO rethink how to initialise. Too many args?
3167
## is it important to let users config connections/issuer etc
32-
def __init__(
33-
self,
34-
webhook_host: str,
35-
webhook_port: int,
36-
admin_url: str,
37-
webhook_base: str = "",
38-
connections: bool = True,
39-
messaging: bool = True,
40-
multitenant: bool = False,
41-
mediation: bool = False,
42-
issuer: bool = True,
43-
action_menu: bool = True,
44-
revocations: bool = True,
45-
api_key: str = None,
46-
tennant_jwt: str = None,
47-
):
48-
68+
webhook_host: str
69+
webhook_port: int
70+
admin_url: str
71+
webhook_base: str = ""
72+
connections: bool = True
73+
messaging: bool = True
74+
multitenant: bool = False
75+
mediation: bool = False
76+
issuer: bool = True
77+
action_menu: bool = True
78+
revocations: bool = True
79+
api_key: str = None
80+
tennant_jwt: str = None
81+
82+
83+
def __post_init__(self):
84+
"""Constructs additional attributes,
85+
and logic defined by attributes set during initial instantiation
86+
"""
87+
4988
self.webhook_site = None
50-
self.admin_url = admin_url
51-
if webhook_base:
52-
self.webhook_base = webhook_base
53-
else:
54-
self.webhook_base = ""
55-
self.webhook_host = webhook_host
56-
self.webhook_port = webhook_port
5789
self.connections_controller = None
58-
59-
headers = {}
60-
61-
if api_key:
62-
headers.update({"X-API-Key": api_key})
63-
64-
if tennant_jwt:
65-
headers.update({'Authorization': 'Bearer ' + tennant_jwt, 'content-type': "application/json"})
66-
67-
self.client_session: ClientSession = ClientSession(headers=headers)
6890

91+
# Construct headers for Client Session and the session itself
92+
self.headers = {}
93+
94+
if self.api_key:
95+
self.headers.update({"X-API-Key": self.api_key})
6996

70-
if connections:
97+
if self.tennant_jwt:
98+
self.headers.update({'Authorization': 'Bearer ' + self.tennant_jwt, 'content-type': "application/json"})
99+
100+
self.client_session: ClientSession = ClientSession(headers=self.headers)
101+
102+
# Instantiate controllers based on the provided attributes
103+
if self.connections:
71104
self.connections = ConnectionsController(self.admin_url, self.client_session)
72-
if messaging:
105+
106+
if self.messaging:
73107
self.messaging = MessagingController(self.admin_url, self.client_session)
74108

75109
self.proofs = ProofController(self.admin_url, self.client_session)
@@ -78,50 +112,9 @@ def __init__(
78112
self.server = ServerController(self.admin_url, self.client_session)
79113
self.oob = OOBController(self.admin_url, self.client_session)
80114

81-
if multitenant:
82-
self.multitenant = MultitenancyController(self.admin_url, self.client_session)
83-
84-
if mediation:
85-
self.mediation = MediationController(self.admin_url, self.client_session)
86-
87-
if issuer:
88-
self.schema = SchemaController(self.admin_url, self.client_session)
89-
self.wallet = WalletController(self.admin_url, self.client_session)
90-
self.definitions = DefinitionsController(self.admin_url, self.client_session)
91-
self.issuer = IssuerController(self.admin_url, self.client_session, self.connections,
92-
self.wallet, self.definitions)
93-
94-
if action_menu:
95-
self.action_menu = ActionMenuController(self.admin_url, self.client_session)
96-
97-
if revocations:
98-
self.revocations = RevocationController(
99-
self.admin_url,
100-
self.client_session
101-
)
102-
103-
# TODO: Determine whether we really want to essentially create a whole new ClientSession object as done below
104-
# Ideally we'd just update the existing session along the lines of self.client_session(headers)
105-
# That does not work, though because it is not callable and updating cannot be achieved reliably
106-
# because headers can be of different type
107-
# from https://docs.aiohttp.org/en/stable/client_reference.html :
108-
# "May be either iterable of key-value pairs or Mapping (e.g. dict, CIMultiDict)."
109-
# So for now let's create a new ClientSession and use all the instances current attributes
110-
# to update every attr using ClientSession
111-
def update_tennant_jwt(self, tennant_jwt):
112-
self.tennant_jwt = tennant_jwt
113-
headers = {'Authorization': 'Bearer ' + tennant_jwt, 'content-type': "application/json"}
114-
self.client_session: ClientSession = ClientSession(headers=headers)
115-
116-
if self.connections:
117-
self.connections = ConnectionsController(self.admin_url, self.client_session)
118-
119-
if self.messaging:
120-
self.messaging = MessagingController(self.admin_url, self.client_session)
121-
122115
if self.multitenant:
123116
self.multitenant = MultitenancyController(self.admin_url, self.client_session)
124-
117+
125118
if self.mediation:
126119
self.mediation = MediationController(self.admin_url, self.client_session)
127120

@@ -130,7 +123,7 @@ def update_tennant_jwt(self, tennant_jwt):
130123
self.wallet = WalletController(self.admin_url, self.client_session)
131124
self.definitions = DefinitionsController(self.admin_url, self.client_session)
132125
self.issuer = IssuerController(self.admin_url, self.client_session, self.connections,
133-
self.wallet, self.definitions)
126+
self.wallet, self.definitions)
134127

135128
if self.action_menu:
136129
self.action_menu = ActionMenuController(self.admin_url, self.client_session)
@@ -141,8 +134,63 @@ def update_tennant_jwt(self, tennant_jwt):
141134
self.client_session
142135
)
143136

137+
138+
def update_tennant_jwt(self, tennant_jwt: str):
139+
"""Update the tenannt JW token attribute and the header
140+
141+
Args:
142+
----
143+
tennant_jwt : str
144+
The tennant's JW token
145+
"""
146+
self.tennant_jwt = tennant_jwt
147+
self.headers.update({'Authorization': 'Bearer ' + tennant_jwt, 'content-type': "application/json"})
148+
self.client_session.headers.update(self.headers)
149+
150+
151+
def update_api_key(self, api_key: str):
152+
"""Update the API Key attribute and the header
153+
154+
Args:
155+
----
156+
api_key : str
157+
The API Key
158+
"""
159+
self.api_key = api_key
160+
self.headers.update({"X-API-Key": api_key})
161+
self.client_session.headers.update(self.headers)
162+
163+
164+
def remove_api_key(self):
165+
"""Removes the API key attribute and corresponding headers from the Client Session"""
166+
self.api_key = None
167+
if 'X-API-Key' in self.client_session.headers:
168+
del self.client_session.headers['X-API-Key']
169+
del self.headers['X-API-Key']
170+
171+
172+
def remove_tennant_jwt(self):
173+
"""Removes the tennant's JW Token attribute and corresponding headers from the Client Session"""
174+
self.tennant_jwt = None
175+
if 'Authorization' in self.client_session.headers:
176+
del self.client_session.headers['Authorization']
177+
del self.headers['Authorization']
178+
if 'content-type' in self.client_session.headers:
179+
del self.client_session.headers['content-type']
180+
del self.headers['content-type']
181+
144182

145183
def register_listeners(self, listeners, defaults=True):
184+
"""Registers the webhook listners
185+
186+
Args:
187+
----
188+
listeners : [dict]
189+
A collection of dictionaries comprised of a "handler": handler (fct) and a "topic":"topicname" key-value pairs
190+
defaults : bool
191+
Whether to connect to the default handlers for connections, basicmessage and present_proof
192+
(default is True)
193+
"""
146194
if defaults:
147195
if self.connections:
148196
pub.subscribe(self.connections.default_handler, "connections")
@@ -154,40 +202,91 @@ def register_listeners(self, listeners, defaults=True):
154202
for listener in listeners:
155203
self.add_listener(listener)
156204

205+
157206
def add_listener(self, listener):
207+
"""Subscribe to a listeners for a topic
208+
209+
Args:
210+
----
211+
listener : dict
212+
A dictionary comprised of a "handler": handler (fct) and a "topic":"topicname" key-value pairs
213+
"""
158214
pub.subscribe(listener["handler"], listener["topic"])
159215

216+
160217
def remove_listener(self, listener):
218+
"""Remove a listener for a topic
219+
220+
Args:
221+
----
222+
listener : dict
223+
A dictionary comprised of a "handler": handler (fct) and a "topic":"topicname" key-value pairs
224+
"""
161225
if pub.isSubscribed(listener["handler"], listener["topic"]):
162226
pub.unsubscribe(listener["handler"], listener["topic"])
163227
else:
164228
logger.debug("Listener not subscribed", listener)
165229

230+
166231
def remove_all_listeners(self, topic: str = None):
232+
"""Remove all listeners for one or all topics
233+
234+
Args:
235+
----
236+
topic : str
237+
The topic to stop listening for (default is None). Default will cause unsubscribing from all topics.
238+
"""
167239
# Note advanced use of function can include both listenerFilter and topicFilter for this
168240
# Add when needed
169241
pub.unsubAll(topicName=topic)
170242

243+
171244
async def listen_webhooks(self):
245+
"""Create a server to listen to webhooks"""
172246
app = web.Application()
173247
app.add_routes([web.post(self.webhook_base + "/topic/{topic}/", self._receive_webhook)])
174248
runner = web.AppRunner(app)
175249
await runner.setup()
176250
self.webhook_site = web.TCPSite(runner, self.webhook_host, self.webhook_port)
177251
await self.webhook_site.start()
178252

253+
179254
async def _receive_webhook(self, request: ClientRequest):
255+
"""Helper to receive webhooks by requesting it
256+
257+
Args:
258+
----
259+
request : ClientRequest
260+
The client request to which the corresponding webhooks shall be received
261+
262+
Returns:
263+
-------
264+
Response:
265+
A response with status 200
266+
"""
180267
topic = request.match_info["topic"]
181268
payload = await request.json()
182269
await self._handle_webhook(topic, payload)
183270
return web.Response(status=200)
184271

272+
185273
async def _handle_webhook(self, topic, payload):
274+
"""Helper handling a webhook
275+
276+
Args:
277+
----
278+
topic : str
279+
The topic to handle webhooks for
280+
payload : dict
281+
A JSON-like dictionary representation of the payload
282+
"""
186283
logging.debug(f"Handle Webhook - {topic}", payload)
187284
pub.sendMessage(topic, payload=payload)
188285
# return web.Response(status=200)
189286

287+
190288
async def terminate(self):
289+
"""Terminate the controller client session and webhook listeners"""
191290
await self.client_session.close()
192291
if self.webhook_site:
193292
await self.webhook_site.stop()

0 commit comments

Comments
 (0)