kongalib 2.0.5__cp314-cp314-macosx_10_15_universal2.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.
kongaui.py ADDED
@@ -0,0 +1,507 @@
1
+ # -*- coding: utf-8 -*-
2
+ # _ _ _ _
3
+ # | | | (_) |
4
+ # | | _____ _ __ __ _ __ _| |_| |__
5
+ # | |/ / _ \| '_ \ / _` |/ _` | | | '_ \
6
+ # | < (_) | | | | (_| | (_| | | | |_) |
7
+ # |_|\_\___/|_| |_|\__, |\__,_|_|_|_.__/
8
+ # __/ |
9
+ # |___/
10
+ #
11
+ # Konga client library, by EasyByte Software
12
+ #
13
+ # https://github.com/easybyte-software/kongalib
14
+
15
+ from __future__ import print_function
16
+
17
+ import sys
18
+ import os, os.path
19
+ import atexit
20
+ import threading
21
+ import time
22
+ import textwrap
23
+ import string
24
+ import colorama
25
+
26
+ import kongautil
27
+
28
+ from kongalib.scripting import _TimeoutBlocker
29
+ from kongalib.scripting import proxy as _proxy
30
+
31
+
32
+
33
+ BUTTON_OK = 0x0001 #: Bottone "Ok"
34
+ BUTTON_YES = 0x0002 #: Bottone "Si"
35
+ BUTTON_YES_ALL = 0x0004 #: Bottone "Si tutti"
36
+ BUTTON_NO = 0x0008 #: Bottone "No"
37
+ BUTTON_NO_ALL = 0x0010 #: Bottone "No tutti"
38
+ BUTTON_CANCEL = 0x0020 #: Bottone "Annulla"
39
+ BUTTON_OPEN = 0x0040 #: Bottone "Apri"
40
+ BUTTON_SAVE = 0x0080 #: Bottone "Salva"
41
+ BUTTON_SAVE_ALL = 0x0100 #: Bottone "Salva tutti"
42
+ BUTTON_CLOSE = 0x0200 #: Bottone "Chiudi"
43
+ BUTTON_DISCARD = 0x0400 #: Bottone "Tralascia"
44
+ BUTTON_APPLY = 0x0800 #: Bottone "Applica"
45
+ BUTTON_RESET = 0x1000 #: Bottone "Ripristina"
46
+ BUTTON_ABORT = 0x2000 #: Bottone "Interrompi"
47
+ BUTTON_RETRY = 0x4000 #: Bottone "Riprova"
48
+ BUTTON_IGNORE = 0x8000 #: Bottone "Ignora"
49
+
50
+ ICON_ERROR = 24 #: Icona di errore
51
+ ICON_QUESTION = 25 #: Icona di domanda
52
+ ICON_WARNING = 26 #: Icona di avviso
53
+ ICON_INFORMATION = 27 #: Icona informativa
54
+
55
+
56
+
57
+ def _shutdown():
58
+ try:
59
+ _proxy.ui.shutdown()
60
+ except:
61
+ pass
62
+ atexit.register(_shutdown)
63
+
64
+ if not _proxy.is_valid():
65
+ colorama.init()
66
+
67
+
68
+
69
+ def _get_term_width():
70
+ import shutil
71
+ return shutil.get_terminal_size(fallback=(80, 24))[0]
72
+
73
+
74
+
75
+ def message_box(text, title='', buttons=BUTTON_OK, icon=ICON_INFORMATION):
76
+ """Mostra una finestra di dialogo modale con titolo *title*, messaggio *text* e icona *icon* (una delle costanti ``ICON_*``). La finestra mostrerà i bottoni identificati
77
+ da *buttons*, che può contenere uno o più costanti ``BUTTON_*`` in *or* tra loro, e ritornerà la costante relativa al bottone selezionato dall'utente per chiudere la
78
+ finestra."""
79
+ if _proxy.is_valid():
80
+ with _TimeoutBlocker():
81
+ return _proxy.ui.message_box(text, title, buttons, icon)
82
+ else:
83
+ print()
84
+ if icon == ICON_WARNING:
85
+ title = colorama.Fore.YELLOW + "WARNING" + (': ' if title else '') + colorama.Fore.RESET + (title or '')
86
+ elif icon == ICON_ERROR:
87
+ title = colorama.Fore.RED + "ERROR" + (': ' if title else '') + colorama.Fore.RESET + (title or '')
88
+ if title:
89
+ print(' ' + colorama.Style.BRIGHT + textwrap.fill(title, width=_get_term_width() - 1) + colorama.Style.RESET_ALL)
90
+ print()
91
+ print(textwrap.fill(text, width=_get_term_width() - 1))
92
+ print()
93
+ buttons_info = [
94
+ ( BUTTON_OK, 'ok' ),
95
+ ( BUTTON_YES, 'yes' ),
96
+ ( BUTTON_YES_ALL, 'yes all' ),
97
+ ( BUTTON_NO, 'no' ),
98
+ ( BUTTON_NO_ALL, 'no all' ),
99
+ ( BUTTON_CANCEL, 'cancel' ),
100
+ ( BUTTON_OPEN, 'open' ),
101
+ ( BUTTON_SAVE, 'save' ),
102
+ ( BUTTON_SAVE_ALL, 'save_all' ),
103
+ ( BUTTON_CLOSE, 'close' ),
104
+ ( BUTTON_DISCARD, 'discard' ),
105
+ ( BUTTON_APPLY, 'apply' ),
106
+ ( BUTTON_RESET, 'reset' ),
107
+ ( BUTTON_ABORT, 'abort' ),
108
+ ( BUTTON_RETRY, 'retry' ),
109
+ ( BUTTON_IGNORE, 'ignore' ),
110
+ ]
111
+ buttons_map = {}
112
+ labels = []
113
+ for bit, label in buttons_info:
114
+ if buttons & bit:
115
+ for i, c in enumerate(label):
116
+ if c not in buttons_map:
117
+ buttons_map[c] = bit
118
+ labels.append('%s(%s)%s' % (label[:i], c, label[i+1:]))
119
+ break
120
+ else:
121
+ for c in string.ascii_letters:
122
+ if c not in buttons_map:
123
+ buttons_map[c] = bit
124
+ labels.append('%s (%s)' % (label, c))
125
+ break
126
+ answer = None
127
+ while answer not in buttons_map:
128
+ try:
129
+ answer = input(', '.join(labels) + ': ')
130
+ except KeyboardInterrupt:
131
+ print(colorama.Fore.YELLOW + "aborted" + colorama.Fore.RESET)
132
+ if buttons & BUTTON_CANCEL:
133
+ return BUTTON_CANCEL
134
+ elif buttons & BUTTON_NO:
135
+ return BUTTON_NO
136
+ elif buttons & BUTTON_CLOSE:
137
+ return BUTTON_CLOSE
138
+ elif buttons & BUTTON_DISCARD:
139
+ return BUTTON_DISCARD
140
+ elif buttons & BUTTON_ABORT:
141
+ return BUTTON_ABORT
142
+ return buttons & ~(buttons - 1)
143
+ return buttons_map[answer]
144
+
145
+
146
+
147
+ def open_file(message=None, specs=None, path='', multi=False):
148
+ """Mostra una finestra di caricamento file con titolo *message. *specs* può essere una lista di tuple ``(extension, description)`` per permettere di caricare solo
149
+ file di tipi specifici; *path* è il percorso predefinito, e *multi* permette di selezionare più di un file da caricare.
150
+ Se *multi* è ``False``, la funzione restituisce il percorso del file selezionato o ``None`` se l'utente ha annullato il caricamento, altrimenti restituisce la lista di file
151
+ selezionati. Se eseguita al di fuori di Konga, questa funzione ignora i parametri *specs*, *path* e *multi*, e l'utente dovrà inserire il percorso completo del file da caricare;
152
+ se verrà inserito un percorso vuoto, la funzione restituirà ``None``."""
153
+ if _proxy.is_valid():
154
+ with _TimeoutBlocker():
155
+ return _proxy.ui.open_file(message, specs, path, multi)
156
+ else:
157
+ if message:
158
+ print(colorama.Style.BRIGHT + textwrap.fill(message, width=_get_term_width() - 1) + colorama.Style.RESET_ALL)
159
+ while True:
160
+ try:
161
+ filename = input('Enter an existing filename to open or none to cancel: ')
162
+ except KeyboardInterrupt:
163
+ print(colorama.Fore.YELLOW + "aborted" + colorama.Fore.RESET)
164
+ return None
165
+ if not filename:
166
+ return None
167
+ if os.path.exists(filename) and os.path.isfile(filename):
168
+ break
169
+ return filename
170
+
171
+
172
+
173
+ def save_file(message=None, spec=None, path=''):
174
+ """Mostra una finestra di salvataggio file con titolo *message. *spec* può essere una tupla nella forma ``(extension, description)`` per permettere di salvare file di un
175
+ tipo specifico; *path* è il percorso predefinito. La funzione restituisce il percorso del file da salvare oppure ``None`` se l'utente ha annullato il salvataggio.
176
+ Se eseguita al di fuori di Konga, questa funzione ignora i parametri *specs* e *path*, e l'utente dovrà inserire il percorso completo del file da salvare;
177
+ se verrà inserito un percorso vuoto, la funzione restituirà ``None``."""
178
+ if _proxy.is_valid():
179
+ with _TimeoutBlocker():
180
+ return _proxy.ui.save_file(message, spec, path)
181
+ else:
182
+ if message:
183
+ print(colorama.Style.BRIGHT + textwrap.fill(message, width=_get_term_width() - 1) + colorama.Style.RESET_ALL)
184
+ try:
185
+ filename = input('Enter filename to be saved or none to cancel: ')
186
+ except KeyboardInterrupt:
187
+ print(colorama.Fore.YELLOW + "aborted" + colorama.Fore.RESET)
188
+ return None
189
+ return filename or None
190
+
191
+
192
+
193
+ def choose_directory(message=None, path=''):
194
+ """Mostra una finestra di selezione directory con titolo *message* e percorso iniziale *path*. La funzione restituisce il percorso della directory selezionata oppure ``None``
195
+ se l'utente ha annullato l'operazione.
196
+ Se eseguita al di fuori di Konga, questa funzione ignora il parametro *path*, e l'utente dovrà inserire il percorso completo della directory; se verrà inserito un percorso
197
+ vuoto, la funzione restituirà ``None``."""
198
+ if _proxy.is_valid():
199
+ with _TimeoutBlocker():
200
+ return _proxy.ui.choose_directory(message, path)
201
+ else:
202
+ if message:
203
+ print(colorama.Style.BRIGHT + textwrap.fill(message, width=_get_term_width() - 1) + colorama.Style.RESET_ALL)
204
+ while True:
205
+ try:
206
+ dirname = input('Enter an existing directory to open or none to cancel: ')
207
+ except KeyboardInterrupt:
208
+ print(colorama.Fore.YELLOW + "aborted" + colorama.Fore.RESET)
209
+ return None
210
+ if not dirname:
211
+ return None
212
+ if os.path.exists(dirname) and os.path.isdir(dirname):
213
+ break
214
+ return dirname
215
+
216
+
217
+
218
+ def select_record(tablename, multi=False, size=None, where_expr=None, code_azienda=None, num_esercizio=None):
219
+ """Mostra una finestra di selezione record di Konga; la finestra mostrerà i record della tabella *tablename* e avrà dimensione *size* (tupla di due elementi nella forma
220
+ ``(width, height)``). *where_expr* può essere un'espressione SQL *WHERE* per filtrare i record selezionabili; *code_azienda* e *code_esercizio* filtrano ulteriormente
221
+ i record visualizzati usando l'azienda e l'esercizio specificati.
222
+ Se *multi* è ``True``, la funzione restituisce una lista di record selezionati sotto forma di ``dict``, altrimenti restituisce il ``dict`` del singolo record selezionato.
223
+ In tutti i casi se l'utente annulla l'operazione, la funzione restituirà ``None``.
224
+
225
+ .. warning::
226
+ Questa funzione è disponibile solo all'interno di Konga; eseguendola da fuori verrà lanciata l'eccezione :class:`kongautil.KongaRequiredError`.
227
+ """
228
+ if _proxy.is_valid():
229
+ with _TimeoutBlocker():
230
+ return _proxy.ui.select_record(tablename, multi, size, where_expr, code_azienda=code_azienda, num_esercizio=num_esercizio)
231
+ else:
232
+ raise kongautil.KongaRequiredError
233
+
234
+
235
+
236
+ def open_progress(title=None, cancellable=True):
237
+ """Mostra una finestra di progresso con titolo *title*, potenzialmente annullabile dall'utente se *cancellable* è ``True``; la funzione ritorna immediatamente.
238
+ Se eseguita fuori da Konga, questa funzione ignora il parametro *cancellable*."""
239
+ if _proxy.is_valid():
240
+ _proxy.ui.open_progress(title or u'Operazione in corso…', cancellable)
241
+ else:
242
+ if title:
243
+ print(colorama.Style.BRIGHT + textwrap.fill(title, width=_get_term_width() - 1) + colorama.Style.RESET_ALL)
244
+ set_progress()
245
+
246
+
247
+
248
+ def close_progress():
249
+ """Nasconde la finestra di progresso precedentemente mostrata con :func:`kongaui.open_progress`."""
250
+ if _proxy.is_valid():
251
+ _proxy.ui.close_progress()
252
+ else:
253
+ print('\033[2K\r', end='')
254
+ sys.stdout.flush()
255
+
256
+
257
+
258
+ def set_progress(progress=None, message=None, state=None):
259
+ """Imposta l'avanzamento corrente nella finestra di progresso precedentemente mostrata con :func:`kongaui.open_progress`. *progress* può essere ``None``
260
+ per specificare un avanzamento indefinito, oppure un valore compreso tra 0 e 100 per specificare la percentuale di avanzamento completata. *message* e
261
+ *state* sono messaggi aggiuntivi da mostrare nella finestra di avanzamento."""
262
+ if _proxy.is_valid():
263
+ _proxy.ui.set_progress(progress, message, state)
264
+ else:
265
+ term_width = _get_term_width()
266
+ def elide(s, width):
267
+ if len(s) > width:
268
+ parts = s.split(' ')
269
+ mid = len(parts) // 2
270
+ before = parts[:mid]
271
+ after = parts[mid:]
272
+ while before or after:
273
+ if len(before) > len(after):
274
+ del before[-1]
275
+ elif after:
276
+ del after[-1]
277
+ s = ' '.join(before) + ' [...] ' + ' '.join(after)
278
+ if len(s) <= width:
279
+ break
280
+ if len(s) > width:
281
+ s = s[:width - 6] + ' [...]'
282
+ return s
283
+ text = []
284
+ if message:
285
+ text.append(message)
286
+ if state:
287
+ text.append(state)
288
+ if not text:
289
+ text.append('Operazione in corso...')
290
+ if (progress is None) or (progress < 0):
291
+ tick = ('\\', '|', '/', '-')[int(time.time() * 5) % 4]
292
+ bar = '%s %s' % (elide(', '.join(text), term_width - 3), tick)
293
+ else:
294
+ progress = ('\u2588' * int((progress * 30) // 100))
295
+ bar = '|%-30s| %s' % (progress, elide(', '.join(text), term_width - 34))
296
+ print('\033[2K\r' + bar, end='')
297
+ sys.stdout.flush()
298
+
299
+
300
+
301
+ def is_progress_aborted():
302
+ """Restituisce ``True`` se l'utente ha annullato la finestra di progresso precedentemente mostrata con :func:`kongaui.open_progress`."""
303
+ if _proxy.is_valid():
304
+ return _proxy.ui.is_progress_aborted()
305
+ else:
306
+ return False
307
+
308
+
309
+
310
+ def open_window(command, key_id=None, key_code=None, code_azienda=None, num_esercizio=None):
311
+ """Apre una finestra di Konga mostrando il comando *command*, ed eventualmente aprendo il record identificato univocamente o da *key_id* (ID del record), o
312
+ dalla tupla (*key_code*, *code_azienda*, *code_esercizio*) (Codice del record, codice dell'azienda e codice dell'esercizio).
313
+
314
+ .. warning::
315
+ Questa funzione è disponibile solo all'interno di Konga; eseguendola da fuori verrà lanciata l'eccezione :class:`kongautil.KongaRequiredError`.
316
+ """
317
+ if _proxy.is_valid():
318
+ _proxy.ui.open_window(command, key_id, key_code, code_azienda, num_esercizio)
319
+ else:
320
+ raise kongautil.KongaRequiredError
321
+
322
+
323
+
324
+ def execute_form(form_data, title=None, message=None, condition=None):
325
+ """Apre un form di immissione dati con titolo *title*; se *message* è specificato, il testo corrispondente sarà visualizzato in alto nella finestra del form.
326
+ *form_data* deve essere una lista di ``dict`` con le specifiche dei campi da mostrare; nel ``dict`` di un singolo campo, l'unica chiave richiesta è ``name``,
327
+ che deve identificare univocamente il nome del campo. E' possibile specificare l'etichetta da mostrare accando al campo stesso tramite la chiave ``label``;
328
+ la tipologia di dato consentità è specificata tramite la chiave ``type``, che può assumere i valori:
329
+
330
+ * ``str``: testo semplice, con possibile lunghezza massima ``length`` se la chiave è specificata;
331
+ * ``password``: parola chiave;
332
+ * ``int``: valore intero;
333
+ * ``decimal``: valore decimale (:class:`kongalib.Decimal`);
334
+ * ``range``: valore intero compreso tra un valore minimo (specificato dalla chiave ``min`` con valore predefinito ``0``) e un valore massimo (specificato dalla chiave ``max`` con valore predefinito ``100``);
335
+ * ``slider``: simile a ``range`` ma viene visualizzato come cursore di selezione valore scorrevole;
336
+ * ``bool``: valore booleano;
337
+ * ``date``: data (``datetime.date``);
338
+ * ``choice``: valore interno che identifica l'indice di una scelta tra quelle specificate nella chiave ``items`` (lista di stringhe);
339
+ * ``listbox``: simile a ``choice`` ma viene visualizzato come lista di elementi da cui fare una scelta;
340
+ * ``load``: nome di file esistente da caricare;
341
+ * ``save``: nome di file da salvare;
342
+ * ``dir``: nome di directory esistente;
343
+ * ``code``: stringa di testo che identifica il codice di un record, la cui tabella è indicata dalla chiave ``table``;
344
+ * ``company_code``: simile a ``code``, specifica l'azienda su cui gli altri campi ``code`` possono essere ricercati;
345
+ * ``accounting_year_code``: simile a ``code``, specifica l'esercizio su cui gli altri campi ``code`` possono essere ricercati;
346
+
347
+ Se presente, la chiave ``default`` permette di specificare il valore predefinito per un dato campo; inoltre se è presente la chiave ``focus`` (con qualsiasi
348
+ valore), il campo corrispondente prenderà il focus all'avvio della finestra. La chiave ``enabled`` permette di specificare anche se abilitare o meno un campo;
349
+ il suo valore può essere un booleano oppure una stringa il cui contenuto viene valutato allo stesso modo del parametro *condition* (vedi sotto).
350
+ Se l'utente annulla il form la funzione restituisce ``None``, altrimenti un ``dict`` le cui chiavi sono i nome dei campi e i valori i dati immessi dall'utente.
351
+ Il parametro *condition*, se presente, permette di specificare una condizione di validazione per il form sotto forma di espressione Python; i nomi dei campi
352
+ specificati in *form_data* saranno disponibili come variabili nell'esecuzione di questa condizione, il cui esito determinerà se consentire o meno l'uscita
353
+ dal form con successo."""
354
+ if _proxy.is_valid():
355
+ with _TimeoutBlocker():
356
+ return _proxy.ui.execute_form(form_data, title, message, condition)
357
+ else:
358
+ import kongalib, decimal, datetime, getpass
359
+ class InvalidInput(RuntimeError):
360
+ pass
361
+ if title:
362
+ print(colorama.Style.BRIGHT + textwrap.fill(title, width=_get_term_width() - 1) + colorama.Style.RESET_ALL)
363
+ if message:
364
+ print(textwrap.fill(message, width=_get_term_width() - 1))
365
+ result = {}
366
+ for entry in form_data:
367
+ if not isinstance(entry, dict):
368
+ raise RuntimeError("Expected dict as form data entry")
369
+ if 'name' not in entry:
370
+ raise RuntimeError("Expected 'name' key in form data entry dict")
371
+ name = str(entry['name'])
372
+ label = str(entry.get('label', name))
373
+ prompt = input
374
+ enabled = entry.get('enabled') or True
375
+ try:
376
+ enabled = eval(str(enabled), result.copy())
377
+ except Exception as e:
378
+ raise RuntimeError("Exception in enable condition: %s" % str(e))
379
+
380
+ wtype = entry.get('type', str)
381
+ if wtype in ('integer', 'int'):
382
+ try:
383
+ default = str(int(entry.get('default', 0)))
384
+ except:
385
+ default = '0'
386
+ def validate(text):
387
+ try:
388
+ return int(text)
389
+ except:
390
+ raise InvalidInput('Expected integer number')
391
+ elif wtype in ('decimal', kongalib.Decimal, decimal.Decimal):
392
+ try:
393
+ default = str(kongalib.Decimal(entry.get('default', 0)))
394
+ except:
395
+ default = str(kongalib.Decimal(0))
396
+ def validate(text):
397
+ try:
398
+ return kongalib.Decimal(text)
399
+ except:
400
+ raise InvalidInput('Expected decimal number')
401
+ elif wtype in ('range', 'slider'):
402
+ try:
403
+ default = str(int(entry.get('default', 0)))
404
+ except:
405
+ default = '0'
406
+ try:
407
+ min_value = int(entry.get('min', 0))
408
+ except:
409
+ min_value = 0
410
+ try:
411
+ max_value = int(entry.get('max', 100))
412
+ except:
413
+ max_value = 100
414
+ label += ' (%d-%d)' % (min_value, max_value)
415
+ def validate(text):
416
+ try:
417
+ value = int(text)
418
+ if (value < min_value) or (value > max_value):
419
+ raise RuntimeError
420
+ return value
421
+ except:
422
+ raise InvalidInput('Expected integer number between %d and %d' % (min_value, max_value))
423
+ elif wtype in ('bool', 'boolean', bool, 'check'):
424
+ try:
425
+ default = 'Y' if bool(entry.get('default', False)) else 'N'
426
+ except:
427
+ default = 'N'
428
+ def validate(text):
429
+ if text.lower() in ('t', 'true', 'y', 'yes', '1'):
430
+ return True
431
+ if text.lower() in ('f', 'false', 'n', 'no', '0'):
432
+ return False
433
+ raise InvalidInput('Expected boolean value')
434
+ elif wtype in ('date', datetime.date):
435
+ try:
436
+ default = datetime.datetime.strptime(entry.get('default', datetime.date.today()), '%Y-%m-%d').date().isoformat()
437
+ except:
438
+ default = datetime.date.today().isoformat()
439
+ def validate(text):
440
+ try:
441
+ return datetime.datetime.strptime(text, '%Y-%m-%d').date()
442
+ except:
443
+ raise InvalidInput('Expected iso date (YYYY-MM-DD)')
444
+ elif wtype in ('choice', 'listbox', 'combobox'):
445
+ items = entry.get('items', [])
446
+ if (not isinstance(items, (tuple, list))) or (not all([ isinstance(item, str) for item in items ])):
447
+ raise RuntimeError("Expected list of strings as 'items' value")
448
+ print(label)
449
+ for index, item in enumerate(items):
450
+ print("%d) %s" % (index + 1, item))
451
+ label = 'Enter selection'
452
+ try:
453
+ default = str(int(entry.get('default', 0)) + 1)
454
+ except:
455
+ default = '1'
456
+ def validate(text):
457
+ try:
458
+ value = int(text)
459
+ if (value < 1) or (value > len(items)):
460
+ raise RuntimeError
461
+ return value - 1
462
+ except:
463
+ raise InvalidInput('Expected integer number between %d and %d' % (1, len(items)))
464
+ else:
465
+ if wtype == 'password':
466
+ prompt = getpass.getpass
467
+ default = None
468
+ else:
469
+ try:
470
+ default = str(entry.get('default', ''))
471
+ except:
472
+ default = ''
473
+ try:
474
+ length = int(entry.get('length', 0))
475
+ except:
476
+ length = 0
477
+ def validate(text):
478
+ if length and (len(text) > length):
479
+ raise InvalidInput('String lengths exceeds maximum size of %d characters' % length)
480
+ return text
481
+ if not enabled:
482
+ value = default
483
+ else:
484
+ if default is not None:
485
+ label += ' [%s]' % default
486
+ while True:
487
+ try:
488
+ value = prompt(label + ': ')
489
+ except KeyboardInterrupt:
490
+ print(colorama.Fore.YELLOW + "aborted" + colorama.Fore.RESET)
491
+ return None
492
+ if (not value) and (default is not None):
493
+ value = default
494
+ try:
495
+ value = validate(value)
496
+ break
497
+ except InvalidInput as e:
498
+ print(colorama.Fore.RED + str(e) + colorama.Fore.RESET)
499
+ result[name] = value
500
+ if condition is not None:
501
+ if not eval(condition, result.copy()):
502
+ print(colorama.Style.BRIGHT + colorama.Fore.RED + "Form input data validation failed; aborted" + colorama.Style.RESET_ALL)
503
+ result = None
504
+ return result
505
+
506
+
507
+