3
3
ClientSession ,
4
4
ClientRequest ,
5
5
)
6
+ from dataclasses import dataclass
6
7
from pubsub import pub
7
8
8
9
from .controllers .connections import ConnectionsController
25
26
26
27
logger = logging .getLogger ("aries_controller" )
27
28
28
- class AriesAgentController :
29
29
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
+
30
66
## TODO rethink how to initialise. Too many args?
31
67
## 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
+
49
88
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
57
89
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 )
68
90
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 })
69
96
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 :
71
104
self .connections = ConnectionsController (self .admin_url , self .client_session )
72
- if messaging :
105
+
106
+ if self .messaging :
73
107
self .messaging = MessagingController (self .admin_url , self .client_session )
74
108
75
109
self .proofs = ProofController (self .admin_url , self .client_session )
@@ -78,50 +112,9 @@ def __init__(
78
112
self .server = ServerController (self .admin_url , self .client_session )
79
113
self .oob = OOBController (self .admin_url , self .client_session )
80
114
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
-
122
115
if self .multitenant :
123
116
self .multitenant = MultitenancyController (self .admin_url , self .client_session )
124
-
117
+
125
118
if self .mediation :
126
119
self .mediation = MediationController (self .admin_url , self .client_session )
127
120
@@ -130,7 +123,7 @@ def update_tennant_jwt(self, tennant_jwt):
130
123
self .wallet = WalletController (self .admin_url , self .client_session )
131
124
self .definitions = DefinitionsController (self .admin_url , self .client_session )
132
125
self .issuer = IssuerController (self .admin_url , self .client_session , self .connections ,
133
- self .wallet , self .definitions )
126
+ self .wallet , self .definitions )
134
127
135
128
if self .action_menu :
136
129
self .action_menu = ActionMenuController (self .admin_url , self .client_session )
@@ -141,8 +134,63 @@ def update_tennant_jwt(self, tennant_jwt):
141
134
self .client_session
142
135
)
143
136
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
+
144
182
145
183
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
+ """
146
194
if defaults :
147
195
if self .connections :
148
196
pub .subscribe (self .connections .default_handler , "connections" )
@@ -154,40 +202,91 @@ def register_listeners(self, listeners, defaults=True):
154
202
for listener in listeners :
155
203
self .add_listener (listener )
156
204
205
+
157
206
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
+ """
158
214
pub .subscribe (listener ["handler" ], listener ["topic" ])
159
215
216
+
160
217
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
+ """
161
225
if pub .isSubscribed (listener ["handler" ], listener ["topic" ]):
162
226
pub .unsubscribe (listener ["handler" ], listener ["topic" ])
163
227
else :
164
228
logger .debug ("Listener not subscribed" , listener )
165
229
230
+
166
231
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
+ """
167
239
# Note advanced use of function can include both listenerFilter and topicFilter for this
168
240
# Add when needed
169
241
pub .unsubAll (topicName = topic )
170
242
243
+
171
244
async def listen_webhooks (self ):
245
+ """Create a server to listen to webhooks"""
172
246
app = web .Application ()
173
247
app .add_routes ([web .post (self .webhook_base + "/topic/{topic}/" , self ._receive_webhook )])
174
248
runner = web .AppRunner (app )
175
249
await runner .setup ()
176
250
self .webhook_site = web .TCPSite (runner , self .webhook_host , self .webhook_port )
177
251
await self .webhook_site .start ()
178
252
253
+
179
254
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
+ """
180
267
topic = request .match_info ["topic" ]
181
268
payload = await request .json ()
182
269
await self ._handle_webhook (topic , payload )
183
270
return web .Response (status = 200 )
184
271
272
+
185
273
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
+ """
186
283
logging .debug (f"Handle Webhook - { topic } " , payload )
187
284
pub .sendMessage (topic , payload = payload )
188
285
# return web.Response(status=200)
189
286
287
+
190
288
async def terminate (self ):
289
+ """Terminate the controller client session and webhook listeners"""
191
290
await self .client_session .close ()
192
291
if self .webhook_site :
193
292
await self .webhook_site .stop ()
0 commit comments