vcon 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vcon/__init__.py +1353 -0
- vcon/cli.py +337 -0
- vcon/filter_plugins/__init__.py +268 -0
- vcon/filter_plugins/impl/whisper.py +145 -0
- vcon/filter_plugins/whisper.py +9 -0
- vcon/security.py +373 -0
- vcon/utils.py +81 -0
- vcon-0.1.dist-info/LICENSE +21 -0
- vcon-0.1.dist-info/METADATA +119 -0
- vcon-0.1.dist-info/RECORD +12 -0
- vcon-0.1.dist-info/WHEEL +5 -0
- vcon-0.1.dist-info/top_level.txt +1 -0
vcon/cli.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for vcon command line interface script functions. Pulled out of vcon CLI
|
|
3
|
+
script file so that it coould more easily be tested with pytest.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import pathlib
|
|
8
|
+
import typing
|
|
9
|
+
import contextlib
|
|
10
|
+
import vcon
|
|
11
|
+
|
|
12
|
+
def get_mime_type(file_name):
|
|
13
|
+
""" derive mimetype from fle extension """
|
|
14
|
+
path = pathlib.PurePath(file_name)
|
|
15
|
+
extension = path.suffix.lower()
|
|
16
|
+
|
|
17
|
+
print("extension: {}".format(extension), file=sys.stderr)
|
|
18
|
+
|
|
19
|
+
if(extension == ".wav"):
|
|
20
|
+
mimetype = vcon.Vcon.MIMETYPE_AUDIO_WAV
|
|
21
|
+
|
|
22
|
+
else:
|
|
23
|
+
raise Exception("MIME type not defined for extension: {}".format(extension))
|
|
24
|
+
|
|
25
|
+
return(mimetype)
|
|
26
|
+
|
|
27
|
+
def main(argv : typing.Optional[typing.Sequence[str]] = None) -> int:
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import sox
|
|
31
|
+
import email
|
|
32
|
+
import email.utils
|
|
33
|
+
import time
|
|
34
|
+
import socket
|
|
35
|
+
|
|
36
|
+
parser = argparse.ArgumentParser("vCon operations such as construction, signing, encryption, verification, decrytpion, filtering")
|
|
37
|
+
input_group = parser.add_mutually_exclusive_group()
|
|
38
|
+
input_group.add_argument("-i", "--infile", metavar='infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
|
|
39
|
+
input_group.add_argument("-n", "--newvcon", action="store_true")
|
|
40
|
+
|
|
41
|
+
parser.add_argument("-o", "--outfile", metavar='outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout)
|
|
42
|
+
|
|
43
|
+
parser.add_argument("-r", "--register-filter-plugin", nargs=3,
|
|
44
|
+
action="append", type=str, default=[])
|
|
45
|
+
|
|
46
|
+
subparsers_command = parser.add_subparsers(dest="command")
|
|
47
|
+
|
|
48
|
+
addparser = subparsers_command.add_parser("add")
|
|
49
|
+
subparsers_add = addparser.add_subparsers(dest="add_command")
|
|
50
|
+
add_in_recording_subparsers = subparsers_add.add_parser("in-recording")
|
|
51
|
+
add_in_recording_subparsers.add_argument("recfile", metavar='recording_file', nargs=1, type=pathlib.Path, default=None)
|
|
52
|
+
add_in_recording_subparsers.add_argument("start", metavar='start_time', nargs=1, type=str, default=None)
|
|
53
|
+
add_in_recording_subparsers.add_argument("parties", metavar='parties', nargs=1, type=str, default=None)
|
|
54
|
+
|
|
55
|
+
add_ex_recording_subparsers = subparsers_add.add_parser("ex-recording")
|
|
56
|
+
add_ex_recording_subparsers.add_argument("recfile", metavar='recording_file', nargs=1, type=pathlib.Path, default=None)
|
|
57
|
+
add_ex_recording_subparsers.add_argument("start", metavar='start_time', nargs=1, type=str, default=None)
|
|
58
|
+
add_ex_recording_subparsers.add_argument("parties", metavar='parties', nargs=1, type=str, default=None)
|
|
59
|
+
add_ex_recording_subparsers.add_argument("url", metavar='url', nargs=1, type=str, default=None)
|
|
60
|
+
|
|
61
|
+
add_in_email_subparsers = subparsers_add.add_parser("in-email")
|
|
62
|
+
add_in_email_subparsers.add_argument("emailfile", metavar='email_file', nargs=1, type=pathlib.Path, default=None)
|
|
63
|
+
add_in_email_subparsers.add_argument("start", metavar='start_time', nargs="?", type=str, default=None)
|
|
64
|
+
add_in_email_subparsers.add_argument("parties", metavar='parties', nargs="?", type=str, default=None)
|
|
65
|
+
|
|
66
|
+
extractparser = subparsers_command.add_parser("extract")
|
|
67
|
+
subparsers_extract = extractparser.add_subparsers(dest="extract_command")
|
|
68
|
+
extract_dialog_subparsers = subparsers_extract.add_parser("dialog")
|
|
69
|
+
extract_dialog_subparsers.add_argument("index", metavar='dialog_index', nargs=1, type=int, default=None)
|
|
70
|
+
|
|
71
|
+
filter_parser = subparsers_command.add_parser("filter")
|
|
72
|
+
fn_help = "Name of filter plugin or default type filter plugin name"
|
|
73
|
+
filter_parser.add_argument("filter_name", metavar="filter_plugin_name", nargs=1, type=str, help=fn_help, default=None, action="append")
|
|
74
|
+
fo_help="JSON object (kwargs) with key name values which are options passed to the filter. (e.g. \'{\"a\" : 1, \"b\" : \"two\"}\' )"
|
|
75
|
+
filter_parser.add_argument("-fo", "--filter-options", metavar='filter_options', nargs=1, type=str, help=fo_help, action="append")
|
|
76
|
+
|
|
77
|
+
sign_parser = subparsers_command.add_parser("sign")
|
|
78
|
+
sign_parser.add_argument("privkey", metavar='private_key_file', nargs=1, type=pathlib.Path, default=None)
|
|
79
|
+
sign_parser.add_argument("pubkey", metavar='public_key_file', nargs='+', type=pathlib.Path, default=None)
|
|
80
|
+
|
|
81
|
+
verify_parser = subparsers_command.add_parser("verify")
|
|
82
|
+
verify_parser.add_argument("pubkey", metavar='public_key_file', nargs='+', type=pathlib.Path, default=None)
|
|
83
|
+
|
|
84
|
+
encrypt_parser = subparsers_command.add_parser("encrypt")
|
|
85
|
+
encrypt_parser.add_argument("pubkey", metavar='public_key_file', nargs='+', type=pathlib.Path, default=None)
|
|
86
|
+
|
|
87
|
+
decrypt_parser = subparsers_command.add_parser("decrypt")
|
|
88
|
+
decrypt_parser.add_argument("privkey", metavar='private_key_file', nargs=1, type=pathlib.Path, default=None)
|
|
89
|
+
decrypt_parser.add_argument("pubkey", metavar='public_key_file', nargs=1, type=pathlib.Path, default=None)
|
|
90
|
+
|
|
91
|
+
args = parser.parse_args(argv)
|
|
92
|
+
|
|
93
|
+
print("args: {}".format(args), file=sys.stderr)
|
|
94
|
+
print("args dir: {}".format(dir(args)), file=sys.stderr)
|
|
95
|
+
|
|
96
|
+
print("command: {}".format(args.command), file=sys.stderr)
|
|
97
|
+
|
|
98
|
+
for filter_plugin_registration in args.register_filter_plugin:
|
|
99
|
+
vcon.filter_plugins.FilterPluginRegistry.register(
|
|
100
|
+
filter_plugin_registration[0],
|
|
101
|
+
filter_plugin_registration[1],
|
|
102
|
+
filter_plugin_registration[2],
|
|
103
|
+
filter_plugin_registration[0],
|
|
104
|
+
replace=True)
|
|
105
|
+
|
|
106
|
+
if(args.command == "sign"):
|
|
107
|
+
print("priv key files: {}".format(len(args.privkey)), file=sys.stderr)
|
|
108
|
+
if(args.privkey[0].exists()):
|
|
109
|
+
print("priv key: {} exists".format(str(args.privkey[0])), file=sys.stderr)
|
|
110
|
+
else:
|
|
111
|
+
print("priv key: {} does NOT exist".format(str(args.privkey[0])), file=sys.stderr)
|
|
112
|
+
|
|
113
|
+
print("pub key files: {}".format(len(args.pubkey)), file=sys.stderr)
|
|
114
|
+
|
|
115
|
+
if(args.command == "verify"):
|
|
116
|
+
if(args.pubkey[0].exists()):
|
|
117
|
+
print("pub key: {} exists".format(str(args.pubkey[0])), file=sys.stderr)
|
|
118
|
+
else:
|
|
119
|
+
print("pub key: {} does NOT exist".format(str(args.pubkey[0])), file=sys.stderr)
|
|
120
|
+
|
|
121
|
+
if(args.command == "encrypt"):
|
|
122
|
+
if(args.pubkey[0].exists()):
|
|
123
|
+
print("pub key: {} exists".format(str(args.pubkey[0])), file=sys.stderr)
|
|
124
|
+
else:
|
|
125
|
+
print("pub key: {} does NOT exist".format(str(args.pubkey[0])), file=sys.stderr)
|
|
126
|
+
|
|
127
|
+
if(args.command == "decrypt"):
|
|
128
|
+
if(args.pubkey[0].exists()):
|
|
129
|
+
print("pub key: {} exists".format(str(args.pubkey[0])), file=sys.stderr)
|
|
130
|
+
else:
|
|
131
|
+
print("pub key: {} does NOT exist".format(str(args.pubkey[0])), file=sys.stderr)
|
|
132
|
+
if(args.privkey[0].exists()):
|
|
133
|
+
print("priv key: {} exists".format(str(args.privkey[0])), file=sys.stderr)
|
|
134
|
+
else:
|
|
135
|
+
print("priv key: {} does NOT exist".format(str(args.privkey[0])), file=sys.stderr)
|
|
136
|
+
|
|
137
|
+
if(args.command == "add"):
|
|
138
|
+
print("add: {}".format(args.add_command), file=sys.stderr)
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
Options:
|
|
142
|
+
[-n | -i <file_name>][-o <file_name>][-r filter_plugin_name plugin_module plugin_class_name [-r ...]]
|
|
143
|
+
|
|
144
|
+
Commands:
|
|
145
|
+
add in-recording <file_name> <start_date> <parties>
|
|
146
|
+
add ex-recording <file_name> <start_date> <parties> <url>
|
|
147
|
+
|
|
148
|
+
filter filter-name [-fo filter-options-dict]
|
|
149
|
+
|
|
150
|
+
sign private_key x5c1[, x5c2]...
|
|
151
|
+
|
|
152
|
+
verify ca_cert
|
|
153
|
+
|
|
154
|
+
encrypt x5c1[, x5c2]... signing_private_key
|
|
155
|
+
|
|
156
|
+
decrypt private_key, ca_cert
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
print("reading", file=sys.stderr)
|
|
161
|
+
|
|
162
|
+
print("out: {}".format(type(args.outfile)), file=sys.stderr)
|
|
163
|
+
print("in: {}".format(type(args.infile)), file=sys.stderr)
|
|
164
|
+
print("new in {}".format(args.newvcon), file=sys.stderr)
|
|
165
|
+
print("filter plugin registrations in {}".format(args.register_filter_plugin), file=sys.stderr)
|
|
166
|
+
in_vcon = vcon.Vcon()
|
|
167
|
+
if(not args.newvcon):
|
|
168
|
+
in_vcon_json = args.infile.read()
|
|
169
|
+
if(in_vcon_json is not None and len(in_vcon_json) > 0):
|
|
170
|
+
in_vcon.loads(in_vcon_json)
|
|
171
|
+
else:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
# Use default serialization for state of vCon
|
|
175
|
+
signed_json = True
|
|
176
|
+
|
|
177
|
+
# By default we output the vCon at the end
|
|
178
|
+
stdout_vcon = True
|
|
179
|
+
|
|
180
|
+
if(args.command == "sign"):
|
|
181
|
+
in_vcon.sign(args.privkey[0], args.pubkey)
|
|
182
|
+
|
|
183
|
+
elif(args.command == "verify"):
|
|
184
|
+
print("state: {}".format(in_vcon._state), file=sys.stderr)
|
|
185
|
+
in_vcon.verify(args.pubkey)
|
|
186
|
+
|
|
187
|
+
# Assuming that generally if someone is verifying the vCon, they want the
|
|
188
|
+
# unsigned JSON version as output.
|
|
189
|
+
signed_json = False
|
|
190
|
+
|
|
191
|
+
elif(args.command == "filter"):
|
|
192
|
+
plugin_name = args.filter_name[0][0].strip(" '\"")
|
|
193
|
+
print("filter name: \"{}\"".format(plugin_name), file=sys.stderr)
|
|
194
|
+
if(args.filter_options is not None):
|
|
195
|
+
try:
|
|
196
|
+
filter_options_dict = json.loads(args.filter_options[0][0].strip(" '\""))
|
|
197
|
+
except Exception as opt_error:
|
|
198
|
+
print(opt_error, file=sys.stderr)
|
|
199
|
+
print("filter options: {}".format(args.filter_options))
|
|
200
|
+
filter_options_dict = None
|
|
201
|
+
if(not isinstance(filter_options_dict, dict)):
|
|
202
|
+
print("filter_options should be a well formed dict. Got: {}".format(args.filter_options[0][0]), file=sys.stderr)
|
|
203
|
+
sys.exit(2)
|
|
204
|
+
else:
|
|
205
|
+
filter_options_dict = {}
|
|
206
|
+
print("filter options: \"{}\"".format(filter_options_dict), file=sys.stderr)
|
|
207
|
+
try:
|
|
208
|
+
plugin = vcon.filter_plugins.FilterPluginRegistry.get(plugin_name)
|
|
209
|
+
print("got plugin for: {}".format(plugin), file=sys.stderr)
|
|
210
|
+
except Exception as pname_error:
|
|
211
|
+
try:
|
|
212
|
+
default_type = vcon.filter_plugins.FilterPluginRegistry.get_type_default_name(plugin_name)
|
|
213
|
+
print("got default type {} plugin for: {}".format(default_type, plugin_name), file=sys.stderr)
|
|
214
|
+
except Exception as ptype_error:
|
|
215
|
+
raise Exception(
|
|
216
|
+
"{} is neither a registered filter plugin name or a default filter plugin type name".format(
|
|
217
|
+
plugin_name)) from pname_error
|
|
218
|
+
|
|
219
|
+
# send print statements that we want to go to stderr
|
|
220
|
+
#
|
|
221
|
+
# Leaving this here commented out for diagnostic purposes. I kind of
|
|
222
|
+
# like it that the unit test fails if there is any print to stdout
|
|
223
|
+
# noise to force clean up.
|
|
224
|
+
#with contextlib.redirect_stdout(sys.stderr):
|
|
225
|
+
in_vcon = in_vcon.filter(plugin_name, **filter_options_dict)
|
|
226
|
+
|
|
227
|
+
elif(args.command == "encrypt"):
|
|
228
|
+
print("state: {}".format(in_vcon._state), file=sys.stderr)
|
|
229
|
+
in_vcon.encrypt(args.pubkey[0])
|
|
230
|
+
|
|
231
|
+
elif(args.command == "decrypt"):
|
|
232
|
+
print("state: {}".format(in_vcon._state), file=sys.stderr)
|
|
233
|
+
in_vcon.decrypt(args.privkey[0], args.pubkey[0])
|
|
234
|
+
|
|
235
|
+
elif(args.command == "add"):
|
|
236
|
+
if(args.add_command in ["in-recording", "ex-recording"]):
|
|
237
|
+
if(not args.recfile[0].exists()):
|
|
238
|
+
raise Exception("Recording file: {} does not exist".format(args.recfile[0]))
|
|
239
|
+
|
|
240
|
+
sox_info = sox.file_info.info(str(args.recfile[0]))
|
|
241
|
+
duration = sox_info["duration"]
|
|
242
|
+
mimetype = get_mime_type(args.recfile[0])
|
|
243
|
+
|
|
244
|
+
with open(args.recfile[0], 'rb') as file_handle:
|
|
245
|
+
body = file_handle.read()
|
|
246
|
+
|
|
247
|
+
parties_object = json.loads(args.parties[0])
|
|
248
|
+
|
|
249
|
+
if(args.add_command == "in-recording"):
|
|
250
|
+
in_vcon.add_dialog_inline_recording(body, args.start[0], duration, parties_object,
|
|
251
|
+
mimetype, str(args.recfile[0]))
|
|
252
|
+
|
|
253
|
+
elif(args.add_command == "ex-recording"):
|
|
254
|
+
in_vcon.add_dialog_external_recording(body, args.start[0], duration, parties_object,
|
|
255
|
+
args.url[0], mimetype, str(args.recfile[0]))
|
|
256
|
+
|
|
257
|
+
elif(args.add_command == "in-email"):
|
|
258
|
+
if(not args.emailfile[0].exists()):
|
|
259
|
+
raise Exception("Email file: {} does not exist".format(args.emailfile[0]))
|
|
260
|
+
|
|
261
|
+
email_message = email.message_from_file(args.emailfile[0].open("r"))
|
|
262
|
+
|
|
263
|
+
# Set subject if its not already set
|
|
264
|
+
if(in_vcon.subject is None or len(in_vcon.subject) == 0):
|
|
265
|
+
subject = email_message.get("subject")
|
|
266
|
+
in_vcon.set_subject(subject)
|
|
267
|
+
|
|
268
|
+
# Get tuple(s) of (name, email_uri) for sender and recipients
|
|
269
|
+
sender = email.utils.parseaddr(email_message.get("from"))
|
|
270
|
+
recipients = email.utils.getaddresses(email_message.get_all("to", []) +
|
|
271
|
+
email_message.get_all("cc", []) +
|
|
272
|
+
email_message.get_all("recent-to", []) +
|
|
273
|
+
email_message.get_all("recent-cc", []))
|
|
274
|
+
|
|
275
|
+
party_indices = []
|
|
276
|
+
for email_address in [sender] + recipients:
|
|
277
|
+
print("email name: {} mailto: {}".format(email_address[0], email_address[1]), file=sys.stderr)
|
|
278
|
+
parties_found = in_vcon.find_parties_by_parameter("mailto", email_address[1])
|
|
279
|
+
if(len(parties_found) == 0):
|
|
280
|
+
parties_found = in_vcon.find_parties_by_parameter("name", email_address[0])
|
|
281
|
+
|
|
282
|
+
if(len(parties_found) == 0):
|
|
283
|
+
party_index = in_vcon.set_party_parameter("mailto", email_address[1])
|
|
284
|
+
in_vcon.set_party_parameter("name", email_address[0], party_index)
|
|
285
|
+
parties_found = [party_index]
|
|
286
|
+
|
|
287
|
+
party_indices.extend(parties_found)
|
|
288
|
+
|
|
289
|
+
if(len(parties_found) > 1):
|
|
290
|
+
print("Warning: multiple parties found matching {}: at indices: {}".format(email_address, parties_found), file=sys.stderr)
|
|
291
|
+
|
|
292
|
+
content_type = email_message.get("content-type")
|
|
293
|
+
file_name = email_message.get_filename()
|
|
294
|
+
#date = time.mktime(email.utils.parsedate(email_message.get("date")))
|
|
295
|
+
date = email.utils.parsedate_to_datetime(email_message.get("date"))
|
|
296
|
+
|
|
297
|
+
email_body = ""
|
|
298
|
+
|
|
299
|
+
if(email_message.is_multipart()):
|
|
300
|
+
body_start = False
|
|
301
|
+
for line in str(email_message).splitlines():
|
|
302
|
+
if(body_start):
|
|
303
|
+
email_body = email_body + line + "\n\r"
|
|
304
|
+
|
|
305
|
+
elif(len(line) == 0):
|
|
306
|
+
body_start = True
|
|
307
|
+
|
|
308
|
+
else:
|
|
309
|
+
email_body = email_message.get_payload()
|
|
310
|
+
|
|
311
|
+
in_vcon.add_dialog_inline_text(email_body, date, 0, party_indices, content_type, file_name)
|
|
312
|
+
|
|
313
|
+
elif(args.command == "extract"):
|
|
314
|
+
if(args.extract_command == "dialog"):
|
|
315
|
+
if(not isinstance(args.index[0], int)):
|
|
316
|
+
raise AttributeError("Dialog index should be type int, not {}".format(type(args.index[0])))
|
|
317
|
+
|
|
318
|
+
dialog_index = args.index[0]
|
|
319
|
+
num_dialogs = len(in_vcon.dialog)
|
|
320
|
+
if(dialog_index > num_dialogs):
|
|
321
|
+
raise AttributeError("Dialog index: {} must be less than the number of dialog in the vCon: {}".format(dialog_index, num_dialogs))
|
|
322
|
+
|
|
323
|
+
recording_bytes = in_vcon.decode_dialog_inline_body(dialog_index)
|
|
324
|
+
stdout_vcon = False
|
|
325
|
+
if(isinstance(recording_bytes, bytes)):
|
|
326
|
+
args.outfile.buffer.write(recording_bytes)
|
|
327
|
+
else:
|
|
328
|
+
args.outfile.write(recording_bytes)
|
|
329
|
+
|
|
330
|
+
#print("vcon._vcon_dict: {}".format(in_vcon._vcon_dict))
|
|
331
|
+
if(stdout_vcon):
|
|
332
|
+
if(in_vcon._state == vcon.VconStates.UNSIGNED and (in_vcon.uuid is None or len(in_vcon.uuid) < 1)):
|
|
333
|
+
in_vcon.set_uuid(socket.gethostname() + ".vcon.dev")
|
|
334
|
+
out_vcon_json = in_vcon.dumps(signed=signed_json)
|
|
335
|
+
args.outfile.write(out_vcon_json)
|
|
336
|
+
|
|
337
|
+
return(0)
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
""" Vcon module providing frameword for filter plugins which take a Von in and provide a Vcon output """
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import importlib
|
|
4
|
+
import typing
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
import traceback
|
|
8
|
+
import logging
|
|
9
|
+
import pythonjsonlogger.jsonlogger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# This package is dependent upon the vcon package only for typing purposes.
|
|
13
|
+
# This creates a circular dependency which we avoid by importing annotations
|
|
14
|
+
# above and importing vcon only if typing.TYPE_CHECKING
|
|
15
|
+
if typing.TYPE_CHECKING:
|
|
16
|
+
from vcon import Vcon
|
|
17
|
+
|
|
18
|
+
# This is cloned from vcon package as we cannot import vcon here due to
|
|
19
|
+
# cyclical import.
|
|
20
|
+
def build_logger(name : str) -> logging.Logger:
|
|
21
|
+
logger = logging.getLogger(name)
|
|
22
|
+
|
|
23
|
+
log_config_filename = "./logging.conf"
|
|
24
|
+
if(os.path.isfile(log_config_filename)):
|
|
25
|
+
logging.config.fileConfig(log_config_filename)
|
|
26
|
+
#print("got logging config", file=sys.stderr)
|
|
27
|
+
else:
|
|
28
|
+
logger.setLevel(logging.DEBUG)
|
|
29
|
+
|
|
30
|
+
# Output to stdout WILL BREAK the Vcon CLI.
|
|
31
|
+
# MUST use stderr.
|
|
32
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
33
|
+
handler.setLevel(logging.DEBUG)
|
|
34
|
+
formatter = pythonjsonlogger.jsonlogger.JsonFormatter( "%(timestamp)s %(levelname)s %(message)s ", timestamp=True)
|
|
35
|
+
handler.setFormatter(formatter)
|
|
36
|
+
logger.addHandler(handler)
|
|
37
|
+
|
|
38
|
+
return(logger)
|
|
39
|
+
logger = build_logger(__name__)
|
|
40
|
+
|
|
41
|
+
class PluginModuleNotFound(Exception):
|
|
42
|
+
""" Thrown when plugin modeule fails to load """
|
|
43
|
+
|
|
44
|
+
class PluginClassNotFound(Exception):
|
|
45
|
+
""" Thrown when plugin class is not found in plugin module """
|
|
46
|
+
|
|
47
|
+
class PluginFilterNotImplemented(Exception):
|
|
48
|
+
""" Thrown when plugin modeule and class are found, but methods are not implemented on filter"""
|
|
49
|
+
|
|
50
|
+
class PluginFilterNotRegistered(Exception):
|
|
51
|
+
""" Thrown when plugin is not found in the FilterPluginRegistry """
|
|
52
|
+
|
|
53
|
+
class PluginFilterAlreadyRegistered(Exception):
|
|
54
|
+
""" Thrown when plugin already exists in the FilterPluginRegistry """
|
|
55
|
+
|
|
56
|
+
class FilterPlugin:
|
|
57
|
+
""" Abstract Vcon filter plugin class. Implementations derive from this clss and must implement the filter method """
|
|
58
|
+
def __init__(self, **options):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def filter(self, in_vcon: Vcon, **options) -> Vcon:
|
|
62
|
+
"""
|
|
63
|
+
Abstract method which performs an operation on an input Vcon and
|
|
64
|
+
provides the modified Vcon as output.
|
|
65
|
+
|
|
66
|
+
Parameters:
|
|
67
|
+
in_vcon (vcon.Vcon) - input Vcon upon which an operation is to be performed by the plugin.
|
|
68
|
+
options (kwargs) - opaque options to the filter method/opearation
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
vcon.Vcon - the modified Vcon
|
|
72
|
+
"""
|
|
73
|
+
raise PluginFilterNotImplemented("{}.filter not implemented".format(type(self)))
|
|
74
|
+
|
|
75
|
+
def __del__(self):
|
|
76
|
+
""" Teardown/uninitialization method for the plugin """
|
|
77
|
+
print("deleting {}".format(self.__class__))
|
|
78
|
+
|
|
79
|
+
class FilterPluginRegistration:
|
|
80
|
+
""" Class containing info and heloer methods on the registration for a single named plugin filter """
|
|
81
|
+
def __init__(self, name: str, module_name: str, class_name: str, description: str):
|
|
82
|
+
self.name = name
|
|
83
|
+
self._module_name = module_name
|
|
84
|
+
self._module_load_attempted = False
|
|
85
|
+
self._module_not_found = False
|
|
86
|
+
self._class_not_found = False
|
|
87
|
+
self._class_name = class_name
|
|
88
|
+
self.description = description
|
|
89
|
+
self._plugin : typing.Union[FilterPlugin, None] = None
|
|
90
|
+
|
|
91
|
+
def import_plugin(self, **options) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Import the package which contains the implementation for the filter
|
|
94
|
+
plugin and instantiate the plugin class.
|
|
95
|
+
|
|
96
|
+
filter plugins are registered and later if used, they are imported.
|
|
97
|
+
When registered, a filter plugin has a name, an implementation module,
|
|
98
|
+
and a class which gets instantiated upon import of the package.
|
|
99
|
+
|
|
100
|
+
Parameters:
|
|
101
|
+
options (kwargs) - options input to the plugin class constructor
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True/False if the plugin module imported successfully and the
|
|
105
|
+
class was instantiated.
|
|
106
|
+
"""
|
|
107
|
+
succeed = False
|
|
108
|
+
if(not self._module_load_attempted):
|
|
109
|
+
try:
|
|
110
|
+
logger.info("importing: {} for registered filter plugin: {}".format(self._module_name, self.name))
|
|
111
|
+
module = importlib.import_module(self._module_name)
|
|
112
|
+
self._module_load_attempted = True
|
|
113
|
+
self._module_not_found = False
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
class_ = getattr(module, self._class_name)
|
|
117
|
+
self._plugin = class_(**options)
|
|
118
|
+
self._class_not_found = False
|
|
119
|
+
succeed = True
|
|
120
|
+
|
|
121
|
+
except AttributeError as ae:
|
|
122
|
+
logger.warning(ae)
|
|
123
|
+
self._class_not_found = True
|
|
124
|
+
|
|
125
|
+
except ModuleNotFoundError as mod_error:
|
|
126
|
+
logger.warning(mod_error)
|
|
127
|
+
logger.warning(traceback.format_exc(limit=-1))
|
|
128
|
+
self._module_not_found = True
|
|
129
|
+
|
|
130
|
+
elif(self._plugin is not None):
|
|
131
|
+
succeed = True
|
|
132
|
+
|
|
133
|
+
return(succeed)
|
|
134
|
+
|
|
135
|
+
def plugin(self, **options) -> typing.Union[FilterPlugin, None]:
|
|
136
|
+
""" Return the plugin filter class for this registration """
|
|
137
|
+
if(not self._module_load_attempted):
|
|
138
|
+
self.import_plugin(**options)
|
|
139
|
+
|
|
140
|
+
return(self._plugin)
|
|
141
|
+
|
|
142
|
+
def filter(self, in_vcon : vcon.Vcon, **options) -> vcon.Vcon:
|
|
143
|
+
if(not self._module_load_attempted):
|
|
144
|
+
self.import_plugin(**options)
|
|
145
|
+
|
|
146
|
+
if(self._module_not_found is True):
|
|
147
|
+
message = "plugin: {} not loaded as module: {} was not found".format(self.name, self._module_name)
|
|
148
|
+
raise PluginModuleNotFound(message)
|
|
149
|
+
|
|
150
|
+
if(self._class_not_found is True):
|
|
151
|
+
message = "plugin: {} not loaded as class: {} not found in module: {}".format(self.name, self._class_name, self._module_name)
|
|
152
|
+
raise PluginClassNotFound(message)
|
|
153
|
+
|
|
154
|
+
plugin = self.plugin(**options)
|
|
155
|
+
if(plugin is None):
|
|
156
|
+
raise Exception("plugin: {} from class: {} module: {} load failed".format(self.name, self._class_name, self._module_name))
|
|
157
|
+
|
|
158
|
+
return(plugin.filter(in_vcon, **options))
|
|
159
|
+
|
|
160
|
+
class FilterPluginRegistry:
|
|
161
|
+
""" class/scope for Vcon filter plugin registrations and defaults for plugin types """
|
|
162
|
+
_registry: typing.Dict[str, FilterPluginRegistration] = {}
|
|
163
|
+
_defaults: typing.Dict[str, str] = {}
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def __add_plugin(plugin: FilterPluginRegistration, replace=False):
|
|
167
|
+
|
|
168
|
+
name_registered = FilterPluginRegistry._registry.get(plugin.name)
|
|
169
|
+
if(name_registered is None):
|
|
170
|
+
# TODO: fix circular import dependency problem
|
|
171
|
+
#import vcon
|
|
172
|
+
#if(vcon.Vcon.attribute_exists(plugin.name)):
|
|
173
|
+
# raise AttributeError("{} is a reserved name already used as a Vcon class or instance name".format(plugin.name))
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
if(name_registered is None or replace):
|
|
177
|
+
|
|
178
|
+
FilterPluginRegistry._registry[plugin.name] = plugin
|
|
179
|
+
else:
|
|
180
|
+
raise PluginFilterAlreadyRegistered("Plugin {} already registered".format(plugin.name))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def register(name: str, module_name: str, class_name: str, description: str, replace: bool = False) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Register a named filter plugin.
|
|
187
|
+
|
|
188
|
+
Parameters:
|
|
189
|
+
name (str) - the name to register the plugin
|
|
190
|
+
|
|
191
|
+
module_name (str) - the module name to import where the plugin class is implmented
|
|
192
|
+
|
|
193
|
+
class_name (str) - the class name for the plugin implementation in the named module
|
|
194
|
+
|
|
195
|
+
description (str) - a text description of what the plugin does
|
|
196
|
+
|
|
197
|
+
replace (bool) - if True replace the already registered plugin of the same name
|
|
198
|
+
if False throw an exception if a plugin of the same name is already register
|
|
199
|
+
"""
|
|
200
|
+
logger.info("Registering FilterPlugin: {}".format(locals()))
|
|
201
|
+
entry = FilterPluginRegistration(name, module_name, class_name, description)
|
|
202
|
+
FilterPluginRegistry.__add_plugin(entry, replace)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def get(name: str) -> FilterPluginRegistration:
|
|
207
|
+
"""
|
|
208
|
+
Returns registration for named plugin
|
|
209
|
+
"""
|
|
210
|
+
plugin_reg = FilterPluginRegistry._registry.get(name, None)
|
|
211
|
+
if(plugin_reg is None):
|
|
212
|
+
raise PluginFilterNotRegistered("Filter plugin {} is not registered".format(name))
|
|
213
|
+
|
|
214
|
+
return(plugin_reg)
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def get_names() -> typing.List[str]:
|
|
218
|
+
"""
|
|
219
|
+
Returns list of plugin names
|
|
220
|
+
"""
|
|
221
|
+
return(FilterPluginRegistry._registry.keys())
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def set_type_default_name(plugin_type: str, name: str) -> None:
|
|
225
|
+
""" Set the default filter name for the given filter type """
|
|
226
|
+
FilterPluginRegistry._defaults[plugin_type] = name
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def get_type_default_name(plugin_type: str) -> typing.Union[str, None]:
|
|
230
|
+
""" Get the default plugin name for the given filter type """
|
|
231
|
+
return(FilterPluginRegistry._defaults.get(plugin_type, None))
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def get_types() -> typing.List[str]:
|
|
235
|
+
"""
|
|
236
|
+
Get the set of FilterPlugin types.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
list(str) - names of all the types for which a default is set.
|
|
240
|
+
"""
|
|
241
|
+
return(FilterPluginRegistry._defaults.keys())
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def get_type_default_plugin(plugin_type: str) -> typing.Union[FilterPluginRegistration, None]:
|
|
245
|
+
""" Get the default FilterPlauginRegistration for the named filter type """
|
|
246
|
+
if(not isinstance(plugin_type, str)):
|
|
247
|
+
raise AttributeError("plugin_type argument should be a string, not {}".format(type(plugin_type)))
|
|
248
|
+
|
|
249
|
+
name = FilterPluginRegistry.get_type_default_name(plugin_type)
|
|
250
|
+
if(name is None):
|
|
251
|
+
raise PluginFilterNotRegistered("Filter plugin default type name {} is not set".format(plugin_type))
|
|
252
|
+
return(FilterPluginRegistry.get(name))
|
|
253
|
+
|
|
254
|
+
class TranscriptionFilter(FilterPlugin):
|
|
255
|
+
# TODO abstraction of transcription filters, iterates through dialogs
|
|
256
|
+
def __init__(self, **options):
|
|
257
|
+
super().__init__(**options)
|
|
258
|
+
|
|
259
|
+
def iterateDialogs(self, in_vcon: Vcon, scope: str ="new"):
|
|
260
|
+
"""
|
|
261
|
+
Iterate through the dialogs in the given vCon
|
|
262
|
+
|
|
263
|
+
Parameters:
|
|
264
|
+
scope = "all", "new"
|
|
265
|
+
"new" = dialogs containing a recording for which there is no transcript
|
|
266
|
+
"all" = all dialogs which contain a recording
|
|
267
|
+
"""
|
|
268
|
+
|