3
3
ClientSession ,
4
4
ClientRequest ,
5
5
)
6
+ from dataclasses import dataclass
6
7
from pubsub import pub
7
8
import sys
8
9
26
27
27
28
logger = logging .getLogger ("aries_controller" )
28
29
29
- class AriesAgentController :
30
30
31
+ @dataclass
32
+ class AriesAgentController :
33
+ """The Aries Agent Controller class
34
+
35
+ This class allows you to interact with Aries by exposing the aca-py API.
36
+
37
+ Attributes
38
+ ----------
39
+ webhook_host : str
40
+ The url of the webhook host
41
+ webhook_port : int
42
+ The exposed port for webhooks on the host
43
+ admin_url : str
44
+ The URL for the Admin API
45
+ webhook_base : str
46
+ The base url for webhooks (default is "")
47
+ connections : bool
48
+ Specify whether to create connecitons (default is True)
49
+ messaging : bool
50
+ Initialise the messaging interface (default is True)
51
+ multitenant : bool
52
+ Initialise the multitenant interface (default is False)
53
+ mediation : bool
54
+ Initialise the mediation interface (default is False)
55
+ issuer : bool
56
+ Initialise the issuer interface (defautl is True)
57
+ action_menu : bool
58
+ Initialise the action menu interface (default is True)
59
+ revocations : bool
60
+ Initialise revocation interface for credentials (default is True)
61
+ api_key : str
62
+ The API key (default is None)
63
+ tenant_jwt: str
64
+ The tenant JW token (default is None)
65
+ """
66
+
31
67
## TODO rethink how to initialise. Too many args?
32
68
## is it important to let users config connections/issuer etc
33
- def __init__ (
34
- self ,
35
- webhook_host : str ,
36
- webhook_port : int ,
37
- admin_url : str ,
38
- webhook_base : str = "" ,
39
- connections : bool = True ,
40
- messaging : bool = True ,
41
- multitenant : bool = False ,
42
- mediation : bool = False ,
43
- issuer : bool = True ,
44
- action_menu : bool = True ,
45
- revocations : bool = True ,
46
- api_key : str = None ,
47
- tennant_jwt : str = None ,
48
- ):
49
-
69
+ webhook_host : str
70
+ webhook_port : int
71
+ admin_url : str
72
+ webhook_base : str = ""
73
+ connections : bool = True
74
+ messaging : bool = True
75
+ multitenant : bool = False
76
+ mediation : bool = False
77
+ issuer : bool = True
78
+ action_menu : bool = True
79
+ revocations : bool = True
80
+ api_key : str = None
81
+ tenant_jwt : str = None
82
+
83
+
84
+ def __post_init__ (self ):
85
+ """Constructs additional attributes,
86
+ and logic defined by attributes set during initial instantiation
87
+ """
88
+
50
89
self .webhook_site = None
51
- self .admin_url = admin_url
52
- if webhook_base :
53
- self .webhook_base = webhook_base
54
- else :
55
- self .webhook_base = ""
56
- self .webhook_host = webhook_host
57
- self .webhook_port = webhook_port
58
90
self .connections_controller = None
59
-
60
- headers = {}
61
-
62
- if api_key :
63
- headers .update ({"X-API-Key" : api_key })
64
-
65
- if tennant_jwt :
66
- headers .update ({'Authorization' : 'Bearer ' + tennant_jwt , 'content-type' : "application/json" })
67
-
68
- self .client_session : ClientSession = ClientSession (headers = headers )
69
91
92
+ # Construct headers for Client Session and the session itself
93
+ self .headers = {}
94
+
95
+ if self .api_key :
96
+ self .headers .update ({"X-API-Key" : self .api_key })
97
+
98
+ if self .tenant_jwt :
99
+ self .headers .update ({'Authorization' : 'Bearer ' + self .tenant_jwt , 'content-type' : "application/json" })
70
100
71
- if connections :
101
+ self .client_session : ClientSession = ClientSession (headers = self .headers )
102
+
103
+ # Instantiate controllers based on the provided attributes
104
+ if self .connections :
72
105
self .connections = ConnectionsController (self .admin_url , self .client_session )
73
- if messaging :
106
+
107
+ if self .messaging :
74
108
self .messaging = MessagingController (self .admin_url , self .client_session )
75
109
76
110
self .proofs = ProofController (self .admin_url , self .client_session )
@@ -79,50 +113,9 @@ def __init__(
79
113
self .server = ServerController (self .admin_url , self .client_session )
80
114
self .oob = OOBController (self .admin_url , self .client_session )
81
115
82
- if multitenant :
83
- self .multitenant = MultitenancyController (self .admin_url , self .client_session )
84
-
85
- if mediation :
86
- self .mediation = MediationController (self .admin_url , self .client_session )
87
-
88
- if issuer :
89
- self .schema = SchemaController (self .admin_url , self .client_session )
90
- self .wallet = WalletController (self .admin_url , self .client_session )
91
- self .definitions = DefinitionsController (self .admin_url , self .client_session )
92
- self .issuer = IssuerController (self .admin_url , self .client_session , self .connections ,
93
- self .wallet , self .definitions )
94
-
95
- if action_menu :
96
- self .action_menu = ActionMenuController (self .admin_url , self .client_session )
97
-
98
- if revocations :
99
- self .revocations = RevocationController (
100
- self .admin_url ,
101
- self .client_session
102
- )
103
-
104
- # TODO: Determine whether we really want to essentially create a whole new ClientSession object as done below
105
- # Ideally we'd just update the existing session along the lines of self.client_session(headers)
106
- # That does not work, though because it is not callable and updating cannot be achieved reliably
107
- # because headers can be of different type
108
- # from https://docs.aiohttp.org/en/stable/client_reference.html :
109
- # "May be either iterable of key-value pairs or Mapping (e.g. dict, CIMultiDict)."
110
- # So for now let's create a new ClientSession and use all the instances current attributes
111
- # to update every attr using ClientSession
112
- def update_tennant_jwt (self , tennant_jwt ):
113
- self .tennant_jwt = tennant_jwt
114
- headers = {'Authorization' : 'Bearer ' + tennant_jwt , 'content-type' : "application/json" }
115
- self .client_session : ClientSession = ClientSession (headers = headers )
116
-
117
- if self .connections :
118
- self .connections = ConnectionsController (self .admin_url , self .client_session )
119
-
120
- if self .messaging :
121
- self .messaging = MessagingController (self .admin_url , self .client_session )
122
-
123
116
if self .multitenant :
124
117
self .multitenant = MultitenancyController (self .admin_url , self .client_session )
125
-
118
+
126
119
if self .mediation :
127
120
self .mediation = MediationController (self .admin_url , self .client_session )
128
121
@@ -131,7 +124,7 @@ def update_tennant_jwt(self, tennant_jwt):
131
124
self .wallet = WalletController (self .admin_url , self .client_session )
132
125
self .definitions = DefinitionsController (self .admin_url , self .client_session )
133
126
self .issuer = IssuerController (self .admin_url , self .client_session , self .connections ,
134
- self .wallet , self .definitions )
127
+ self .wallet , self .definitions )
135
128
136
129
if self .action_menu :
137
130
self .action_menu = ActionMenuController (self .admin_url , self .client_session )
@@ -142,8 +135,63 @@ def update_tennant_jwt(self, tennant_jwt):
142
135
self .client_session
143
136
)
144
137
138
+
139
+ def update_tenant_jwt (self , tenant_jwt : str ):
140
+ """Update the tenant JW token attribute and the header
141
+
142
+ Args:
143
+ ----
144
+ tenant_jwt : str
145
+ The tenant's JW token
146
+ """
147
+ self .tenant_jwt = tenant_jwt
148
+ self .headers .update ({'Authorization' : 'Bearer ' + tenant_jwt , 'content-type' : "application/json" })
149
+ self .client_session .headers .update (self .headers )
150
+
151
+
152
+ def update_api_key (self , api_key : str ):
153
+ """Update the API Key attribute and the header
154
+
155
+ Args:
156
+ ----
157
+ api_key : str
158
+ The API Key
159
+ """
160
+ self .api_key = api_key
161
+ self .headers .update ({"X-API-Key" : api_key })
162
+ self .client_session .headers .update (self .headers )
163
+
164
+
165
+ def remove_api_key (self ):
166
+ """Removes the API key attribute and corresponding headers from the Client Session"""
167
+ self .api_key = None
168
+ if 'X-API-Key' in self .client_session .headers :
169
+ del self .client_session .headers ['X-API-Key' ]
170
+ del self .headers ['X-API-Key' ]
171
+
172
+
173
+ def remove_tenant_jwt (self ):
174
+ """Removes the tenant's JW Token attribute and corresponding headers from the Client Session"""
175
+ self .tenant_jwt = None
176
+ if 'Authorization' in self .client_session .headers :
177
+ del self .client_session .headers ['Authorization' ]
178
+ del self .headers ['Authorization' ]
179
+ if 'content-type' in self .client_session .headers :
180
+ del self .client_session .headers ['content-type' ]
181
+ del self .headers ['content-type' ]
182
+
145
183
146
184
def register_listeners (self , listeners , defaults = True ):
185
+ """Registers the webhook listners
186
+
187
+ Args:
188
+ ----
189
+ listeners : [dict]
190
+ A collection of dictionaries comprised of a "handler": handler (fct) and a "topic":"topicname" key-value pairs
191
+ defaults : bool
192
+ Whether to connect to the default handlers for connections, basicmessage and present_proof
193
+ (default is True)
194
+ """
147
195
try :
148
196
if defaults :
149
197
if self .connections :
@@ -160,15 +208,31 @@ def register_listeners(self, listeners, defaults=True):
160
208
logger .warn (f"Register webhooks listeners failed! { exc !r} occurred." )
161
209
162
210
211
+
163
212
def add_listener (self , listener ):
213
+ """Subscribe to a listeners for a topic
214
+
215
+ Args:
216
+ ----
217
+ listener : dict
218
+ A dictionary comprised of a "handler": handler (fct) and a "topic":"topicname" key-value pairs
219
+ """
164
220
try :
165
221
pub .subscribe (listener ["handler" ], listener ["topic" ])
166
222
except Exception as exc :
167
223
print (f"Adding webhooks listener failed! { exc !r} occurred." )
168
224
logger .warn (f"Adding webhooks listener failed! { exc !r} occurred." )
169
225
170
226
227
+
171
228
def remove_listener (self , listener ):
229
+ """Remove a listener for a topic
230
+
231
+ Args:
232
+ ----
233
+ listener : dict
234
+ A dictionary comprised of a "handler": handler (fct) and a "topic":"topicname" key-value pairs
235
+ """
172
236
try :
173
237
if pub .isSubscribed (listener ["handler" ], listener ["topic" ]):
174
238
pub .unsubscribe (listener ["handler" ], listener ["topic" ])
@@ -179,7 +243,15 @@ def remove_listener(self, listener):
179
243
logger .warn (f"Removing webhooks listener failed! { exc !r} occurred." )
180
244
181
245
246
+
182
247
def remove_all_listeners (self , topic : str = None ):
248
+ """Remove all listeners for one or all topics
249
+
250
+ Args:
251
+ ----
252
+ topic : str
253
+ The topic to stop listening for (default is None). Default will cause unsubscribing from all topics.
254
+ """
183
255
# Note advanced use of function can include both listenerFilter and topicFilter for this
184
256
# Add when needed
185
257
try :
@@ -189,7 +261,9 @@ def remove_all_listeners(self, topic: str = None):
189
261
logger .warn (f"Removing all webhooks listeners failed! { exc !r} occurred." )
190
262
191
263
264
+
192
265
async def listen_webhooks (self ):
266
+ """Create a server to listen to webhooks"""
193
267
try :
194
268
app = web .Application ()
195
269
app .add_routes ([web .post (self .webhook_base + "/topic/{topic}/" , self ._receive_webhook )])
@@ -202,7 +276,20 @@ async def listen_webhooks(self):
202
276
logger .warn (f"Listening webhooks failed! { exc !r} occurred." )
203
277
204
278
279
+
205
280
async def _receive_webhook (self , request : ClientRequest ):
281
+ """Helper to receive webhooks by requesting it
282
+
283
+ Args:
284
+ ----
285
+ request : ClientRequest
286
+ The client request to which the corresponding webhooks shall be received
287
+
288
+ Returns:
289
+ -------
290
+ Response:
291
+ A response with status 200
292
+ """
206
293
topic = request .match_info ["topic" ]
207
294
try :
208
295
payload = await request .json ()
@@ -212,7 +299,17 @@ async def _receive_webhook(self, request: ClientRequest):
212
299
logger .warn (f"Receiving webhooks failed! { exc !r} occurred." )
213
300
214
301
302
+
215
303
async def _handle_webhook (self , topic , payload ):
304
+ """Helper handling a webhook
305
+
306
+ Args:
307
+ ----
308
+ topic : str
309
+ The topic to handle webhooks for
310
+ payload : dict
311
+ A JSON-like dictionary representation of the payload
312
+ """
216
313
try :
217
314
logging .debug (f"Handle Webhook - { topic } " , payload )
218
315
pub .sendMessage (topic , payload = payload )
@@ -221,7 +318,9 @@ async def _handle_webhook(self, topic, payload):
221
318
logger .warn (f"Handling webhooks failed! { exc !r} occurred when trying to handle this topic: { topic } " )
222
319
223
320
321
+
224
322
async def terminate (self ):
323
+ """Terminate the controller client session and webhook listeners"""
225
324
try :
226
325
await self .client_session .close ()
227
326
if self .webhook_site :
0 commit comments