kongalib 2.0.4__cp314-cp314t-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.

Potentially problematic release.


This version of kongalib might be problematic. Click here for more details.

@@ -0,0 +1,813 @@
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
+
16
+ from __future__ import absolute_import
17
+
18
+ import asyncio
19
+ import nest_asyncio
20
+ import inspect
21
+
22
+ from kongalib import *
23
+
24
+
25
+ nest_asyncio.apply()
26
+
27
+
28
+ class AsyncClient(Client):
29
+ """La classe AsyncClient, analogamente alla classe :class:`~kongalib.Client`, permette di connettersi ad un server Konga e di eseguire
30
+ comandi sullo stesso; la differenza è nel fatto che questa classe è costruita esplicitamente per lavorare con *asyncio* di Python, ed
31
+ è disponibile solo se si usa Python >= 3.6.
32
+
33
+ Tutti i metodi che esistono nella classe Client e che possono essere invocati sia in maniera asincrona (specificando le callback di
34
+ *success*, *error* e *progress*) che sincrona (generalmente omettendo di specificare la callback di *success*), nella classe AsyncClient
35
+ accettano eventualmente la sola callback di *progress*, in quanto vengono sempre eseguiti in maniera asincrona tramite l'event loop di
36
+ asyncio, che si assume sia in esecuzione. La *progress* viene eseguita in un thread separato, ed ha la forma ``progress(type, completeness, state, userdata)``;
37
+ i parametri interessanti di questa callback sono *completeness* (percentuale di completamento, ossia un numero che varia da 0.0 a 100.0;
38
+ se -1.0 indica una percentuale di completamento indefinita), *state* (stringa che specifica l'eventuale stato corrente dell'operazione)
39
+ e *userdata*, che è un parametro aggiuntivo che viene normalmente passato alla chiamata asincrona dall'utente per tenere traccia di un
40
+ eventuale stato.
41
+
42
+ Come per la classe Client, oggetti di classe AsyncClient possono essere usati come contesti per il costrutto ``with``: all'ingresso del
43
+ blocco verrà iniziata una transazione, mentre in uscita verrà eseguita una commit o una rollback della stessa a seconda che ci sia stata
44
+ o meno un'eccezione all'interno del blocco di istruzioni. Da notare che dal momento che siamo in ambito asincrono, andrà usato il
45
+ costrutto ``async with`` al posto del semplice ``with``.
46
+ """
47
+
48
+ def _safe_set_result(self, future, result):
49
+ if not future.done():
50
+ future.set_result(result)
51
+
52
+ def _safe_set_exception(self, future, e):
53
+ if not future.done():
54
+ future.set_exception(e)
55
+
56
+ def _make_progress(self, future, progress, userdata):
57
+ def callback(ptype, completeness, state, dummy):
58
+ loop = future.get_loop()
59
+ try:
60
+ if future.cancelled() or (progress is None):
61
+ result = not future.cancelled()
62
+ else:
63
+ if asyncio.iscoroutinefunction(progress):
64
+ coro = progress
65
+ else:
66
+ async def coro(*args):
67
+ return progress(*args)
68
+ result = asyncio.run_coroutine_threadsafe(coro(ptype, completeness, state, userdata), loop).result()
69
+ if result is False:
70
+ return False
71
+ except Exception as e:
72
+ result = False
73
+ loop.call_soon_threadsafe(self._safe_set_exception, future, e)
74
+ return result
75
+ return callback
76
+
77
+ def _make_error(self, future):
78
+ def error(errno, *args):
79
+ loop = future.get_loop()
80
+ if isinstance(errno, int) and (errno in (ABORTED, EXECUTE_ABORTED)):
81
+ errno = Error(errno, '')
82
+ if isinstance(errno, Error):
83
+ if errno.errno in (ABORTED, EXECUTE_ABORTED):
84
+ loop.call_soon_threadsafe(future.cancel)
85
+ else:
86
+ loop.call_soon_threadsafe(self._safe_set_exception, future, errno)
87
+ elif isinstance(errno, Exception):
88
+ loop.call_soon_threadsafe(self._safe_set_exception, future, errno)
89
+ else:
90
+ if len(args) > 0:
91
+ errstr = ensure_text(args[0])
92
+ else:
93
+ errstr = '<unknown>'
94
+ loop.call_soon_threadsafe(self._safe_set_exception, future, Error(errno, errstr))
95
+ return error
96
+
97
+ def _make_success_tuple(self, future, count):
98
+ def success(*args):
99
+ loop = future.get_loop()
100
+ if count == 0:
101
+ loop.call_soon_threadsafe(self._safe_set_result, future, None)
102
+ elif count == 1:
103
+ loop.call_soon_threadsafe(self._safe_set_result, future, args[0])
104
+ else:
105
+ loop.call_soon_threadsafe(self._safe_set_result, future, args[:count])
106
+ return success
107
+
108
+ def _make_success(self, future, log=None, finalize_output=None):
109
+ def success(output, *args):
110
+ loop = future.get_loop()
111
+ answer = output[OUT_LOG] or []
112
+ error_list = ErrorList(answer)
113
+ if output[OUT_ERRNO] == OK:
114
+ if error_list.errno != OK:
115
+ if log is None:
116
+ loop.call_soon_threadsafe(self._safe_set_exception, future, error_list)
117
+ else:
118
+ error_list.prepare_log(log)
119
+ if log.has_errors():
120
+ loop.call_soon_threadsafe(self._safe_set_exception, future, error_list)
121
+ else:
122
+ try:
123
+ if finalize_output is not None:
124
+ output = finalize_output(output)
125
+ except Exception as e:
126
+ loop.call_soon_threadsafe(self._safe_set_exception, future, e)
127
+ else:
128
+ loop.call_soon_threadsafe(self._safe_set_result, future, output)
129
+ else:
130
+ try:
131
+ if finalize_output is not None:
132
+ output = finalize_output(output)
133
+ except Exception as e:
134
+ loop.call_soon_threadsafe(self._safe_set_exception, future, e)
135
+ else:
136
+ loop.call_soon_threadsafe(self._safe_set_result, future, output)
137
+ else:
138
+ loop.call_soon_threadsafe(self._safe_set_exception, future, ErrorList.from_error(output[OUT_ERRNO], output[OUT_ERROR]))
139
+ return success
140
+
141
+ def _execute(self, cmd, in_params, out_params=None, progress=None, log=None):
142
+ def finalize(output):
143
+ if out_params:
144
+ if callable(out_params):
145
+ return out_params(output)
146
+ elif isinstance(out_params, (tuple, list)):
147
+ return tuple([ output[param] for param in out_params ])
148
+ else:
149
+ return output[out_params]
150
+ else:
151
+ return None
152
+ fut = asyncio.get_running_loop().create_future()
153
+ self._impl.execute(cmd, in_params or {}, DEFAULT_EXECUTE_TIMEOUT, self._make_success(fut, log, finalize), self._make_error(fut), self._make_progress(fut, progress, None))
154
+ return fut
155
+
156
+ async def __aenter__(self):
157
+ await self.begin_transaction()
158
+ return self
159
+
160
+ async def __aexit__(self, exc_type, exc_value, exc_traceback):
161
+ if exc_type is None:
162
+ await self.commit_transaction()
163
+ else:
164
+ await self.rollback_transaction()
165
+
166
+ def as_sync(self):
167
+ """Ritorna un oggetto :class:`~kongalib.Client` equivalente a questo client, preservando le connessioni già presenti.
168
+ """
169
+ return Client(self._impl)
170
+
171
+ def list_servers(self, timeout=DEFAULT_DISCOVER_TIMEOUT, port=0, progress=None, userdata=None):
172
+ """Esegue una scansione della rete locale alla ricerca dei server Konga disponibili, attendendo al massimo *timeout* millisecondi
173
+ per una risposta. *port* specifica la porta da cui far partire la scansione (default = 51967); sono controllate le successive 10
174
+ porte UDP con intervallo di 20 porte (quindi di default vengono scansionate le porte 51967, 51987, 52007, ... 52147). La funzione
175
+ restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una lista di ``dict``, le cui chiavi principali
176
+ sono *host*, *port*, *name* e *description*.
177
+ """
178
+ fut = asyncio.get_running_loop().create_future()
179
+ self._impl.list_servers(timeout, port, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata))
180
+ return fut
181
+
182
+ def connect(self, server=None, host=None, port=0, options=None, timeout=DEFAULT_CONNECT_TIMEOUT, progress=None, userdata=None):
183
+ """Tenta una connessione ad un server Konga. Il server a cui ci si vuole connettere può essere specificato in due modi: tramite i
184
+ parametri *host* e *port*, oppure tramite un ``dict`` *server* che deve contenere almeno le chiavi *host* e *port*. Alternativamente,
185
+ se *server* è una stringa e *host* non è specificato, viene assunta come *host*. Se *host* include una specifica di porta e *port* è ``0``,
186
+ *port* viene ottenuta dalla specifica contenuta nella stringa di *host*. Il parametro *options* può essere un ``dict`` contenente opzioni
187
+ aggiuntive per la connessione; al momento le opzioni supportate sono:
188
+
189
+ - ``tenant_key`` (*str*): chiave del tenant per stabilire la connessione con un server multitenant.
190
+
191
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà un ``dict`` contenente le informazioni
192
+ sulla connessione stabilita.
193
+ """
194
+ if (server is None) and (host is None):
195
+ raise ValueError("either 'host' or 'server' parameter must be specified")
196
+ if isinstance(server, str) and (host is None):
197
+ host = server
198
+ server = None
199
+ if isinstance(host, str) and (port is None) and (':' in host):
200
+ pos = host.rfind(':')
201
+ host = host[:pos]
202
+ try:
203
+ port = int(host[pos+1:])
204
+ except:
205
+ raise ValueError("Invalid port value embedded in host string")
206
+ fut = asyncio.get_running_loop().create_future()
207
+ self._impl.connect(server, host or '', port, options, timeout, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata))
208
+ return fut
209
+
210
+ def disconnect(self):
211
+ """Disconnette il server attualmente connesso, oppure non fa nulla se non si è al momento connessi."""
212
+ self._impl.disconnect()
213
+
214
+ def get_id(self):
215
+ """Restituisce un ID numerico univoco assegnato dal server alla connessione con questo client, o 0 se non si è connessi."""
216
+ return self._impl.get_id()
217
+
218
+ def get_connection_info(self):
219
+ """Restituisce un ``dict`` con informazioni sulla connessione corrente, o ``None`` se non si è connessi."""
220
+ return self._impl.get_connection_info()
221
+
222
+ def execute(self, command, data=None, timeout=DEFAULT_EXECUTE_TIMEOUT, progress=None, userdata=None, log=None):
223
+ fut = asyncio.get_running_loop().create_future()
224
+ self._impl.execute(command, data or {}, timeout, self._make_success(fut, log), self._make_error(fut), self._make_progress(fut, progress, userdata))
225
+ return fut
226
+
227
+ def interrupt(self):
228
+ """Interrompe tutte le operazioni al momento in esecuzione da parte di questo client."""
229
+ self._impl.interrupt()
230
+
231
+ def get_data_dictionary(self, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
232
+ """Restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà il dizionario dei dati disponibile sul server
233
+ attualmente connesso, sotto forma di oggetto di classe :class:`kongalib.DataDictionary`.
234
+ """
235
+ fut = asyncio.get_running_loop().create_future()
236
+ uuid = self.get_connection_info().get('uuid', None)
237
+ with Client.DATA_DICTIONARY_LOCK:
238
+ if uuid is None:
239
+ data = None
240
+ else:
241
+ data = Client.DATA_DICTIONARY_CACHE.get(uuid, None)
242
+ if data is None:
243
+ def success(d, userdata):
244
+ with Client.DATA_DICTIONARY_LOCK:
245
+ d = DataDictionary(d)
246
+ Client.DATA_DICTIONARY_CACHE[uuid] = d
247
+ fut.get_loop().call_soon_threadsafe(self._safe_set_result, fut, d)
248
+ self._impl.get_data_dictionary(success, self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
249
+ else:
250
+ fut.set_result(data)
251
+ return fut
252
+
253
+ def list_drivers(self, configured=True, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
254
+ """Restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà la lista dei driver di database presenti
255
+ sul server attualmente connesso, oppure lancia un'eccezione :class:`~kongalib.Error` su errore. Ogni elemento della lista restituita
256
+ è un ``dict`` che comprende la chiavi *name*, *version* e *description*. Se *configured* è False, tutti i driver installati sul
257
+ server sono restituiti, altrimenti verranno restituite solo le informazioni sui driver configurati correttamente ed in esecuzione
258
+ sul server.
259
+ """
260
+ fut = asyncio.get_running_loop().create_future()
261
+ self._impl.list_drivers(configured, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
262
+ return fut
263
+
264
+ def list_databases(self, driver=None, quick=False, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
265
+ """Restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà la lista dei database disponibili sul server
266
+ corrente, appartenenti a tutti o ad uno specifico *driver*. La lista viene tornata sotto forma di ``dict``, le cui chiavi sono i nomi
267
+ dei driver e i valori le liste dei database appartenenti allo specifico driver. Ogni database nelle liste è un ``dict`` che contiene
268
+ almeno le chiavi *name*, *desc*, *uuid*, *created_ts* e *modified_ts*. L'eccezione :class:`~kongalib.Error` viene lanciata se si
269
+ verifica un errore. Se *quick* è ``True``, la funzione ritorna il più velocemente possibile ma la scansione dei database disponibili
270
+ potrebbe risultare ancora incompleta.
271
+ """
272
+ fut = asyncio.get_running_loop().create_future()
273
+ self._impl.list_databases(driver, quick, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
274
+ return fut
275
+
276
+ def create_database(self, password, driver, name, desc='', progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
277
+ """Crea un nuovo database sul server attualmente connesso; il database avrà nome *name* e descrizione *desc*.
278
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà l'UUID del nuovo database;
279
+ se si verifica un errore viene lanciata l'eccezione :class:`~kongalib.Error`.
280
+
281
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
282
+ """
283
+ fut = asyncio.get_running_loop().create_future()
284
+ self._impl.create_database(password, driver, name, desc, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
285
+ return fut
286
+
287
+ def open_database(self, driver, name, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
288
+ """Apre un database rendendolo il database attivo per la connessione corrente. La funzione restituisce un oggetto ``asyncio.Future`` il
289
+ cui risultato una volta completato sarà un ``dict`` con le informazioni sul database connesso, oppure viene lanciata l'eccezione
290
+ :class:`~kongalib.Error` in caso di errore.
291
+ """
292
+ fut = asyncio.get_running_loop().create_future()
293
+ self._impl.open_database(driver, name, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
294
+ return fut
295
+
296
+ def close_database(self, backup=False, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
297
+ """Chiude il database attivo sulla connessione corrente, restituendo un oggetto ``asyncio.Future`` per l'esecuzione asincrona; in caso
298
+ di errore verrà lanciata l'eccezione :class:`~kongalib.Error`.
299
+
300
+ .. note:: Se *backup* è ``True``, il server esegue un backup automatico del database prima di chiuderlo.
301
+ """
302
+ fut = asyncio.get_running_loop().create_future()
303
+ self._impl.close_database(backup, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
304
+ return fut
305
+
306
+ def upgrade_database(self, password, driver, name, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
307
+ """Aggiorna il database specificato all'ultima versione disponibile.
308
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una tupla (log, old_version, new_version),
309
+ dove il log dell'operazione è sotto forma di una lista di stringhe, oppure viene lanciata l'eccezione :class:`~kongalib.Error` in caso
310
+ di errore.
311
+
312
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
313
+ """
314
+ fut = asyncio.get_running_loop().create_future()
315
+ self._impl.upgrade_database(password, driver, name, self._make_success_tuple(fut, 3), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
316
+ return fut
317
+
318
+ def delete_database(self, password, driver, name, delete_cloud_data=None, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
319
+ """Cancella il database specificato. Se *delete_cloud_data* è ``None`` (valore predefinito) la cancellazione sarà negata nel caso ci siano
320
+ dati binari legati al database al momento presenti nel cloud; in caso contrario i dati binari saranno o meno cancellati dal cloud in base
321
+ al valore del parametro.
322
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
323
+ caso di errore.
324
+
325
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
326
+ """
327
+ fut = asyncio.get_running_loop().create_future()
328
+ self._impl.delete_database(password, driver, name, delete_cloud_data, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
329
+ return fut
330
+
331
+ def query(self, query, native=False, full_column_names=False, collapse_blobs=False, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
332
+ """Esegue una query SQL sul database attivo nella connessione corrente. Se *native* è ``True``, la query viene passata al driver
333
+ del database senza essere interpretata, permettendo l'esecuzione di query native per l'RDBMS sottostante. La funzione restituisce un oggetto
334
+ ``asyncio.Future`` il cui risultato una volta completato sarà una tupla nella forma ``(affected_rows, column_names, result_set)``;
335
+ *affected_rows* è il numero di righe coinvolte nella query di UPDATE/DELETE, *column_names* è una lista di nomi di colonne per il result set,
336
+ mentre *result_set* è una lista di righe risultati della query, dove ogni riga è una lista di valori corrispondenti alle colonne restituite in
337
+ *column_names*. In caso di errore viene lanciata l'eccezione :class:`~kongalib.Error`.
338
+
339
+ .. note:: Se *full_column_names* è ``False``, *column_names* includerà i nomi delle colonne senza nome tabella, altrimenti saranno
340
+ inclusi i nomi completi delle colonne. Se *collapse_blobs* è ``True``, i dati di tipo BLOB binari verranno restituiti come ``[...]``.
341
+ """
342
+ fut = asyncio.get_running_loop().create_future()
343
+ self._impl.query_database(query, native, full_column_names, collapse_blobs, self._make_success_tuple(fut, 3), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
344
+ return fut
345
+
346
+ def backup_database(self, password, backup_name, driver, name, auto=True, overwrite=False, position=0, store_index=False, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
347
+ """Esegue un backup del database specificato sul server attualmente connesso. Se *auto* è ``False``, è necessario specificare un nome
348
+ per il backup tramite *backup_name*, altrimenti il backup viene considerato automatico ed un nome univoco è assegnato dal server. Se
349
+ *overwrite* è ``False`` ed un backup con lo stesso nome esiste già sul server, non sarà possibile eseguire il backup. *position*
350
+ permette di specificare dove eseguire il backup, ed è una combinazione delle costanti :const:`kongalib.BACKUP_ON_COMPUTER` e
351
+ :const:`kongalib.BACKUP_ON_CLOUD`, mentre *store_index* specifica se includere l'indice di ricerca full-text nel backup.
352
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
353
+ caso di errore.
354
+
355
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
356
+ """
357
+ fut = asyncio.get_running_loop().create_future()
358
+ self._impl.backup_database(password, backup_name, driver, name, auto, overwrite, position, store_index, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
359
+ return fut
360
+
361
+ def restore_database(self, password, backup_name, driver, name, change_uuid=True, overwrite=False, position=0, restore_index=True, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
362
+ """Ripristina un database a partire da un backup effettuato in precedenza sul server connesso. Se *overwrite* è False ed esiste un
363
+ database gestito da *driver* con lo stesso nome, la funzione riporterà errore. *change_uuid* specifica se cambiare l'UUID associato al
364
+ database oppure se ripristinare quello originale; se si hanno database con lo stesso nome gestiti da driver diversi è opportuno che
365
+ almeno l'UUID per essi sia diverso, altrimenti si può incorrere in problemi di aliasing. *position* specifica da dove prendere il
366
+ backup da rispristinare, e deve essere una delle costanti :const:`kongalib.BACKUP_ON_COMPUTER` o :const:`kongalib.BACKUP_ON_CLOUD`;
367
+ *restore_index* invece permette di specificare se ripristinare o meno l'indice di ricerca qualora fosse contenuto all'interno del backup.
368
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
369
+ caso di errore.
370
+
371
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
372
+ """
373
+ fut = asyncio.get_running_loop().create_future()
374
+ self._impl.restore_database(password, backup_name, driver, name, change_uuid, overwrite, position, restore_index, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
375
+ return fut
376
+
377
+ def list_backups(self, position=0, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
378
+ """Ottiene la lista dei backup disponibili sul server connesso.
379
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una lista di backup; ogni backup è un
380
+ ``dict`` che contiene almeno le chiavi *backup_name*, *uuid*, *date* e *size*; se si verifica un errore viene lanciata l'eccezione
381
+ :class:`~kongalib.Error`.
382
+ """
383
+ fut = asyncio.get_running_loop().create_future()
384
+ self._impl.list_backups(position, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
385
+ return fut
386
+
387
+ def delete_backup(self, password, backup_name, position, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
388
+ """Cancella il backup identificato da *backup_name* dal server connesso.
389
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
390
+ caso di errore.
391
+
392
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
393
+ """
394
+ fut = asyncio.get_running_loop().create_future()
395
+ self._impl.delete_backup(password, backup_name, position, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
396
+ return fut
397
+
398
+ def optimize_database(self, password, driver, name, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
399
+ """Esegue una ottimizzazione del database specificato sul server attualmente connesso.
400
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
401
+ caso di errore.
402
+
403
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
404
+ """
405
+ fut = asyncio.get_running_loop().create_future()
406
+ self._impl.optimize_database(password, driver, name, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
407
+ return fut
408
+
409
+ def repair_database(self, password, driver, name, output, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
410
+ """Prova a riparare il database danneggiato specificato, salvando il database recuperato in *output*.
411
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
412
+ caso di errore.
413
+
414
+ .. note:: Non tutti i driver di database supportano questa operazione, e il recupero del database potrebbe fallire in ogni caso.
415
+
416
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
417
+ """
418
+ fut = asyncio.get_running_loop().create_future()
419
+ self._impl.repair_database(password, driver, name, output, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
420
+ return fut
421
+
422
+ def index_database(self, password, driver, name, reset=False, run=True, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
423
+ """Esegue una indicizzazione del database specificato sul server attualmente connesso. Se *reset* è ``False``, l'indicizzazione è
424
+ incrementale, ovvero l'indice viene modificato per tenere conto solo dei record inseriti, modificati o cancellati dall'ultima
425
+ indicizzazione; se invece *reset* è ``True`` l'indice viene prima cancellato e poi, se *run* è anch'esso ``True``, viene ricreato completamente.
426
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
427
+ caso di errore.
428
+
429
+ .. warning:: E' necessaria la *password* del server per poter eseguire questa operazione.
430
+ """
431
+ fut = asyncio.get_running_loop().create_future()
432
+ self._impl.index_database(password, driver, name, reset, run, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
433
+ return fut
434
+
435
+ def list_clients(self, full=True, any=False, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
436
+ fut = asyncio.get_running_loop().create_future()
437
+ self._impl.list_clients(full, any, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
438
+ return fut
439
+
440
+ def get_client_info(self, id, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
441
+ fut = asyncio.get_running_loop().create_future()
442
+ self._impl.get_client_info(id, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
443
+ return fut
444
+
445
+ def kill_client(self, id, password, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
446
+ fut = asyncio.get_running_loop().create_future()
447
+ self._impl.kill_client(id, password, self._make_success_tuple(fut, 0), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
448
+ return fut
449
+
450
+ def authenticate(self, username, password, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT, new_password=None):
451
+ """Effettua un accesso al database attivo sulla connessione corrente, identificando l'utente tramite i parametri *username* e *password*.
452
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà un ``dict`` con informazioni dettagliate
453
+ sull'utente autenticato, oppure viene lanciata l'eccezione :class:`~kongalib.Error` in caso di errore.
454
+ """
455
+ fut = asyncio.get_running_loop().create_future()
456
+ self._impl.authenticate(username, password, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout, new_password)
457
+ return fut
458
+
459
+ def full_text_search(self, text, limit, progress=None, userdata=None, timeout=DEFAULT_EXECUTE_TIMEOUT):
460
+ """Esegue una ricerca full-text sul database attivo sulla connessione corrente, limitando la ricerca di *text* a *limit* risultati.
461
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una lista di risultati, dove ogni
462
+ risultato è ``dict`` con almeno le chiavi *score*, *tablename*, *id* e *display*; in caso di errore viene lanciata l'eccezione
463
+ :class:`~kongalib.Error`.
464
+ """
465
+ fut = asyncio.get_running_loop().create_future()
466
+ self._impl.full_text_search(text, limit, self._make_success_tuple(fut, 1), self._make_error(fut), self._make_progress(fut, progress, userdata), None, timeout)
467
+ return fut
468
+
469
+ def get_permissions(self, user_id):
470
+ return self._execute(CMD_GET_PERMISSIONS, {
471
+ IN_USER_ID: user_id
472
+ }, OUT_PERMISSIONS)
473
+
474
+ def set_permissions(self, user_id, permissions):
475
+ return self._execute(CMD_SET_PERMISSIONS, {
476
+ IN_USER_ID: user_id,
477
+ IN_PERMISSIONS: permissions
478
+ })
479
+
480
+ def begin_transaction(self, pause_indexing=False, deferred=False):
481
+ """Inizia una transazione sul database attivo nella connessione corrente. Se *pause_indexing* è ``True``, l'indicizzazione del
482
+ database è disabilitata sul server.
483
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
484
+ caso di errore.
485
+ """
486
+ flags = 0
487
+ if pause_indexing:
488
+ flags |= 0x1
489
+ if deferred:
490
+ flags |= 0x2
491
+ return self._execute(CMD_BEGIN_TRANSACTION, {
492
+ IN_FLAGS: flags
493
+ })
494
+
495
+ def commit_transaction(self, resume_indexing=False):
496
+ """Esegue una COMMIT della transazione sul database attivo nella connessione corrente. Se *resume_indexing* è ``True``, l'indicizzazione
497
+ del database è abilitata sul server.
498
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
499
+ caso di errore.
500
+ """
501
+ flags = 0
502
+ if resume_indexing:
503
+ flags |= 0x1
504
+ return self._execute(CMD_COMMIT_TRANSACTION, {
505
+ IN_FLAGS: flags
506
+ })
507
+
508
+ def rollback_transaction(self, resume_indexing=False):
509
+ """Esegue un ROLLBACK della transazione sul database attivo nella connessione corrente. Se *resume_indexing* è ``True``, l'indicizzazione
510
+ del database è abilitata sul server.
511
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
512
+ caso di errore.
513
+ """
514
+ flags = 0
515
+ if resume_indexing:
516
+ flags |= 0x1
517
+ return self._execute(CMD_ROLLBACK_TRANSACTION, {
518
+ IN_FLAGS: flags
519
+ })
520
+
521
+ def lock_resource(self, command, row_id=None):
522
+ """Tenta di eseguire il blocco della risorsa identificata da *command*. Se *row_id* è diverso da ``None``, è possibile eseguire il
523
+ blocco di una singola riga di una tabella del database.
524
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una tupla ``(result, owner_data)``,
525
+ dove *owner_data* è un ``dict`` contenente informazioni sull'utente che detiene già il blocco della risorsa in caso fosse già bloccata,
526
+ oppure lancia un'eccezione :class:`~kongalib.Error` in caso di errore.
527
+ """
528
+ return self._execute(CMD_LOCK, {
529
+ IN_COMMAND_NAME: command,
530
+ IN_ROW_ID: row_id
531
+ }, ( OUT_ANSWER, OUT_OWNER_DATA ))
532
+
533
+ def unlock_resource(self, command, row_id=None):
534
+ """Rilascia il blocco della risorsa identificata da *tablename* e *row_id*.
535
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona, e verrà lanciata l'eccezione :class:`~kongalib.Error` in
536
+ caso di errore.
537
+ """
538
+ return self._execute(CMD_UNLOCK, {
539
+ IN_COMMAND_NAME: command,
540
+ IN_ROW_ID: row_id
541
+ }, OUT_ANSWER)
542
+
543
+ def select_data(self, tablename, fieldnamelist=None, where_expr=None, order_by=None, order_desc=False, offset=0, count=None, get_total=False, exist=None, progress=None):
544
+ """Genera ed esegue una SELECT sul server per ottenere una lista di risultati, a partire dalla tabella *tablename*.
545
+ *fieldnamelist* è una lista di nomi dei campi da ottenere; se un campo fk_X di *tablename* è una foreign key, si può accedere ai
546
+ campi della tabella collegata Y specificando "fk_X.Campo_di_Y"; la JOIN corrispondente verrà generata e gestita automaticamente dal
547
+ server. Analogamente, si possono creare catene di JOIN implicite facendo riferimenti multipli di campi foreign key, per esempio
548
+ "fk_X.fk_Y.fk_Z.Campo_di_Z".
549
+
550
+ Se *where_expr* non è ``None``, può essere il corpo di una espressione WHERE SQL, e può contenere riferimenti nella stessa forma di
551
+ *fieldnamelist*, per esempio "(Campo_di_X = 1) AND (fk_X.Campo_di_Y > 5)".
552
+
553
+ *order_by* può essere un nome di campo per cui ordinare i risultati, dove *order_desc* specifica se ordinare in modo ascendente o discendente.
554
+ *offset* e *count* permettono di restituire risultati solo a partire dal numero *offset*, e limitandosi a *count* risultati.
555
+
556
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato dipende dal valore del parametro *get_total*.
557
+ Se *get_total* è ``True``, il risultato sarà una tupla nella forma ``(result_set, total_rows, exist_results)``; *total_rows* sarà il numero
558
+ totale di righe come se *offset* e *limit* non fossero stati specificati, mentre *exist_results* sarà un ``dict`` le cui chiavi saranno gli
559
+ ID specificati nel parametro *exist*, e i valori saranno ``True`` o ``False`` a seconda che il corrispettivo ID sia presente nel database
560
+ per la tabella *tablename* oppure no.
561
+ Se *get_total* è ``False``, il risultato sarà il solo *result_set*, ossia una lista di righe risultato della query, dove ogni riga è una
562
+ lista di valori.
563
+ """
564
+ if isinstance(fieldnamelist, str):
565
+ fieldnamelist = [ fieldnamelist ]
566
+ elif fieldnamelist:
567
+ fieldnamelist = list(fieldnamelist)
568
+ return self._execute(CMD_SELECT, {
569
+ IN_TABLE_NAME: tablename,
570
+ IN_COLUMN_NAMES: fieldnamelist,
571
+ IN_WHERE_CLAUSE: where(where_expr),
572
+ IN_ORDER_BY: order_by,
573
+ IN_ORDER_DESC: order_desc,
574
+ IN_OFFSET: offset,
575
+ IN_ROW_COUNT: count,
576
+ IN_GET_TOTAL_ROWS: get_total,
577
+ IN_GET_ROWS_EXIST: exist,
578
+ }, ( OUT_RESULT_SET, OUT_TOTAL_ROWS, OUT_EXIST ) if get_total else OUT_RESULT_SET, progress=progress)
579
+
580
+ def select_data_as_dict(self, tablename, fieldnamelist=None, where_expr=None, order_by=None, order_desc=False, offset=0, count=None, get_total=False, progress=None):
581
+ """Esattamente come :meth:`.select_data`, ma l'oggetto ``asyncio.Future`` restituito una volta completato ritornerà un *result_set* sotto
582
+ forma di lista di ``dict``, anzichè una lista di liste."""
583
+ if isinstance(fieldnamelist, str):
584
+ fieldnamelist = [ fieldnamelist ]
585
+ elif fieldnamelist:
586
+ fieldnamelist = list(fieldnamelist)
587
+ def get_result(output):
588
+ names = output.get(OUT_COLUMN_NAMES, None) or fieldnamelist
589
+ result_set = [dict(list(zip(names, row))) for row in output[OUT_RESULT_SET] ]
590
+ if get_total:
591
+ return ( result_set, output[OUT_TOTAL_ROWS], output[OUT_EXIST] )
592
+ else:
593
+ return result_set
594
+ return self._execute(CMD_SELECT, {
595
+ IN_TABLE_NAME: tablename,
596
+ IN_COLUMN_NAMES: fieldnamelist,
597
+ IN_WHERE_CLAUSE: where(where_expr),
598
+ IN_ORDER_BY: order_by,
599
+ IN_ORDER_DESC: order_desc,
600
+ IN_OFFSET: offset,
601
+ IN_ROW_COUNT: count,
602
+ IN_GET_TOTAL_ROWS: get_total,
603
+ }, get_result, progress=progress)
604
+
605
+ def get_record(self, tablename, code=None, id=None, field_names=None, row_extra_field_names=None, code_azienda=None, num_esercizio=None, mode=None, mask_binary=None, flags=GET_FLAG_DEFAULT, progress=None):
606
+ """Ottiene il record completo della tabella *tablename*, sotto forma di ``dict``. Il record può essere identificato in due modi: o
607
+ tramite il solo *id*, oppure tramite la specifica dei parametri *code*, *code_azienda* e *num_esercizio*.
608
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà un ``dict`` con il record ottenuto;
609
+ verrà lanciata un'eccezione :class:`~kongalib.Error` in caso di errore.
610
+ """
611
+ if (id is None) and (code is None):
612
+ raise ValueError('Either code or id must be specified')
613
+ def get_result(output):
614
+ data = output[OUT_DICT_DATA]
615
+ data['@checksum'] = output[OUT_CHECKSUM]
616
+ return data
617
+ return self._execute(CMD_GET, {
618
+ IN_TABLE_NAME: tablename,
619
+ IN_ROW_ID: id,
620
+ IN_CODE: code,
621
+ IN_CODE_AZIENDA: code_azienda,
622
+ IN_NUM_ESERCIZIO: num_esercizio,
623
+ IN_FLAGS: flags,
624
+ IN_COLUMN_NAMES: field_names,
625
+ IN_ROW_EXTRA_FIELDS: row_extra_field_names,
626
+ }, get_result, progress=progress)
627
+
628
+ def insert_record(self, tablename, data, code_azienda=None, num_esercizio=None, log=None, progress=None):
629
+ """Inserisce un nuovo record nella tabella *tablename*. Il nuovo record, i cui dati sono passati nel ``dict`` *data*, sarà un record
630
+ condiviso con tutte le aziende del database se *code_azienda* e *num_esercizio* sono ``None``, altrimenti apparterrà solo all'azienda e
631
+ all'esercizio specificati.
632
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una tupla nella forma ``(id, code)``,
633
+ dove *id* è l'ID univoco assegnato al record dal server, mentre *code* è il codice del record (che può essere diverso da quello passato
634
+ in *data* se sono attivi i codici automatici per *tablename*); in caso di errore verrà lanciata un'eccezione di classe
635
+ :class:`~kongalib.Error` o :class:`~kongalib.ErrorList`. Al termine dell'operazione, se *log* è un oggetto di classe :class:`OperationLog`,
636
+ esso riceverà ogni eventuale messaggio di log prodotto dal server durante l'inserimento.
637
+ """
638
+ return self._execute(CMD_INSERT_FROM_DICT, {
639
+ IN_TABLE_NAME: tablename,
640
+ IN_CODE_AZIENDA: code_azienda,
641
+ IN_NUM_ESERCIZIO: num_esercizio,
642
+ IN_DICT_DATA: data
643
+ }, ( OUT_ID, OUT_CODE ), progress=progress, log=log)
644
+
645
+ def update_record(self, tablename, data, code=None, id=None, code_azienda=None, num_esercizio=None, log=None, progress=None):
646
+ """Aggiorna un record esistente nella tabella *tablename*. Il record, i cui dati da aggiornare sono passati nel ``dict`` *data*, può
647
+ essere identificato in due modi: o tramite il solo *id*, oppure tramite la specifica dei parametri *code*, *code_azienda* e *num_esercizio*.
648
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona; in caso di errore verrà lanciata un'eccezione di classe
649
+ :class:`~kongalib.Error` o :class:`~kongalib.ErrorList`. Al termine dell'operazione, se *log* è un oggetto di classe :class:`OperationLog`,
650
+ esso riceverà ogni eventuale messaggio di log prodotto dal server durante l'aggiornamento.
651
+ """
652
+ return self._execute(CMD_UPDATE_FROM_DICT, {
653
+ IN_TABLE_NAME: tablename,
654
+ IN_ROW_ID: id,
655
+ IN_CODE: code,
656
+ IN_CODE_AZIENDA: code_azienda,
657
+ IN_NUM_ESERCIZIO: num_esercizio,
658
+ IN_DICT_DATA: data
659
+ }, progress=progress, log=log)
660
+
661
+ def delete_record(self, tablename, code=None, id=None, code_azienda=None, num_esercizio=None, log=None, progress=None):
662
+ """Cancella un record dalla tabella *tablename*. Il record può essere identificato in due modi: o tramite il solo *id*, oppure tramite
663
+ la specifica dei parametri *code*, *code_azienda* e *num_esercizio*.
664
+ La funzione restituisce un oggetto ``asyncio.Future`` per l'esecuzione asincrona; in caso di errore verrà lanciata un'eccezione di classe
665
+ :class:`~kongalib.Error` o :class:`~kongalib.ErrorList`. Al termine dell'operazione, se *log* è un oggetto di classe :class:`OperationLog`,
666
+ esso riceverà ogni eventuale messaggio di log prodotto dal server durante la cancellazione.
667
+ """
668
+ return self._execute(CMD_DELETE_FROM_CODE, {
669
+ IN_TABLE_NAME: tablename,
670
+ IN_ROW_ID: id,
671
+ IN_CODE: code,
672
+ IN_CODE_AZIENDA: code_azienda,
673
+ IN_NUM_ESERCIZIO: num_esercizio,
674
+ }, progress=progress, log=log)
675
+
676
+ def code_exists(self, tablename, code, code_azienda, num_esercizio, extra_where=None):
677
+ """Controlla l'esistenza del codice *code* nella tabella *tablename* per l'azienda e l'esercizio specificati in *code_azienda* e *num_esercizio*.
678
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà un valore booleano; in caso di errore verrà
679
+ lanciata un'eccezione di classe :class:`~kongalib.Error`.
680
+ """
681
+ return self._execute(CMD_CODE_EXISTS, {
682
+ IN_TABLE_NAME: tablename,
683
+ IN_CODE: code,
684
+ IN_CODE_AZIENDA: code_azienda,
685
+ IN_NUM_ESERCIZIO: num_esercizio,
686
+ IN_EXTRA_WHERE: where(extra_where),
687
+ }, OUT_EXISTS)
688
+
689
+ def get_next_available_code(self, tablename, code_azienda, num_esercizio, dry_run=False, force=False):
690
+ return self._execute(CMD_GET_NEXT_CODE, {
691
+ IN_TABLE_NAME: tablename,
692
+ IN_CODE_AZIENDA: code_azienda,
693
+ IN_NUM_ESERCIZIO: num_esercizio,
694
+ IN_DRY_RUN: dry_run,
695
+ IN_FORCE: force,
696
+ }, OUT_CODE)
697
+
698
+ def get_last_npfe(self, code_azienda, num_esercizio):
699
+ return self._execute(CMD_GET_LAST_NPFE, {
700
+ IN_CODE_AZIENDA: code_azienda,
701
+ IN_NUM_ESERCIZIO: num_esercizio,
702
+ }, OUT_NPFE)
703
+
704
+ def start_elab(self, command, params, code_azienda, num_esercizio, log=None, progress=None, tx=True):
705
+ return self._execute(CMD_START_ELAB, {
706
+ IN_COMMAND: command,
707
+ IN_PARAMS: params,
708
+ IN_CODE_AZIENDA: code_azienda,
709
+ IN_NUM_ESERCIZIO: num_esercizio,
710
+ IN_TX: tx,
711
+ }, OUT_DATA, progress=progress, log=log)
712
+
713
+ def list_binaries(self, field_or_tablename, id, type=None, progress=None, full=False):
714
+ """Ottiene la lista dei dati binari associati ad una scheda del database, identificata da *field_or_tablename* (che può essere un nome
715
+ tabella o un campo da cui risolvere il nome tabella) e *id*. La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato
716
+ una volta completato sarà una lista di tuple, in cui la n-esima tupla ha la forma ``( Tipo, NomeAllegato, NomeOriginale )``; *Tipo*
717
+ è un intero ed è uno dei valori della *Choice* ``Resources``, *NomeAllegato* è il nome assegnato internamente a Konga per identificare
718
+ univocamente il contenuto binario, mentre *NomeOriginale* è il nome del file originale da cui è stato caricato il contenuto. Se *type*
719
+ è specificato, la funzione filtrerà i risultati in base ad esso, ritornando solo le tuple con il *Tipo* corretto. Se *full* è ``True``
720
+ la n-esima tupla ritornata avrà tre valori in più corrispondenti all'etichetta dell'immagine aggiuntiva (se specificata), al codice
721
+ della tipologia dell'allegato e ai metadati associati (se presenti), e la tupla avrà quindi la forma
722
+ ``( Tipo, NomeAllegato, NomeOriginale, Etichetta, CodiceTipologia, Metadati )``.
723
+ """
724
+ def get_result(output):
725
+ return [ tuple(row) for row in output[OUT_LIST] if ((type is None) or (row[0] == type)) ]
726
+ return self._execute(CMD_LIST_BINARIES, {
727
+ IN_FIELD_NAME: field_or_tablename,
728
+ IN_ROW_ID: id,
729
+ IN_FULL: full,
730
+ }, get_result, progress=progress)
731
+
732
+ def fetch_image(self, fieldname, id, type, progress=None, label=None):
733
+ """Piccolo wrapper alla funzione :meth:`.fetch_binary`, dedicato alle immagini, con l'unica differenza che l'oggetto ``asyncio.Future``
734
+ restituito una volta completato avrà come valore di ritorno direttamente il contenuto binario dell'immagine.
735
+ """
736
+ return self._execute(CMD_FETCH_BINARY, {
737
+ IN_FIELD_NAME: fieldname,
738
+ IN_ROW_ID: id,
739
+ IN_TYPE: type,
740
+ IN_LABEL: label,
741
+ }, OUT_DATA, progress=progress)
742
+
743
+ def fetch_binary(self, field_or_tablename, id, type, filename=None, check_only=False, progress=None, label=None, with_extra=False):
744
+ """Carica un contenuto binario dal server. *field_or_tablename* può essere un nome tabella o un campo da cui risolvere il nome tabella;
745
+ questa tabella unita a *id* identificano la scheda del database da cui caricare la risorsa; *type* è uno dei valori della *Choice*
746
+ ``Resources``, mentre *filename* e *label* hanno senso solo per identificare rispettivamente le risorse di tipo documento ed immagine
747
+ aggiuntiva.
748
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà una tupla di quattro elementi:
749
+ ( *dati*, *filename*, *original_filename*, *checksum* ). *dati* sono i dati binari che sono stati caricati dal server; *filename* è
750
+ il nome file interno con cui è identificata la risorsa, *original_filename* è il nome del file originale che è stato specificato
751
+ all'atto del salvataggio della risorsa sul server, mentre *checksum* è un checksum dei dati. Se *with_extra* è ``True``, la funzione
752
+ ritorna sei elementi, e gli elementi aggiuntivi sono un ``dict`` con i metadata associati alla risorsa, o ``None`` se non ci sono
753
+ metadati associati, e il codice della tipologia allegato se presente. Se *check_only* è ``True``, i dati binari della risorsa non
754
+ verranno effettivamente caricati dal dispositivo di archiviazione in cui sono depositati, e *dati* sarà ``None``; questa modalità è
755
+ utile per verificare l'esistenza di una risorsa e il suo checksum senza effettivamente caricarla da remoto (nel caso di archiviazione
756
+ su cloud il caricamento potrebbe essere lento)."""
757
+ if (type == 0) and (not filename):
758
+ raise ValueError('filename must be specified for document type resources')
759
+ if with_extra:
760
+ out_params = ( OUT_DATA, OUT_FILENAME, OUT_ORIGINAL_FILENAME, OUT_DATA_CHECKSUM, OUT_METADATA, OUT_CODE_TIPOLOGIA )
761
+ else:
762
+ out_params = ( OUT_DATA, OUT_FILENAME, OUT_ORIGINAL_FILENAME, OUT_DATA_CHECKSUM )
763
+ return self._execute(CMD_FETCH_BINARY, {
764
+ IN_FIELD_NAME: field_or_tablename,
765
+ IN_ROW_ID: id,
766
+ IN_TYPE: type,
767
+ IN_FILENAME: filename,
768
+ IN_LABEL: label,
769
+ IN_CHECK: check_only,
770
+ }, out_params, progress=progress)
771
+
772
+ def store_binary(self, field_or_tablename, id, type, filename=None, original_filename=None, data=None, desc=None, force_delete=False, code_azienda=None, progress=None, label=None, metadata=None, code_tipologia=None, log=None):
773
+ """Salva un contenuto binario sul server. *field_or_tablename* può essere un nome tabella o un campo da cui risolvere il nome tabella;
774
+ questa tabella unita a *id* identificano la scheda a cui abbinare la risorsa; *type* è uno dei valori della *Choice*``Resources``;
775
+ *filename* permette di specificare un nome file interno con cui identificare la risorsa (se ``None`` il server genererà un nome univoco
776
+ automaticamente); *original_filename* è il nome file originale i cui dati si stanno salvando sul server; *data* sono i dati binari
777
+ effettivi; *desc* è la descrizione da abbinare alla risorsa; *code_azienda* infine identifica l'azienda su cui si sta operando, mentre
778
+ *code_tipologia* permette di specificare una tipologia da abbinare al dati. Per le risorse di tipo immagine aggiuntiva è necessario
779
+ specificare una *label* da abbinare all'immagine per identificarla univocamente. *metadata* può essere un ``dict`` in cui sia chiavi che
780
+ valori siano delle semplici stringhe, e permette di specificare dei metadati aggiuntivi associati alla risorsa binaria che si sta inserendo.
781
+ La funzione restituisce un oggetto ``asyncio.Future`` il cui risultato una volta completato sarà il nome del file interno usato dal
782
+ server per identificare la risorsa, che come detto sopra è uguale a *filename* se quest'ultimo è diverso da ``None``, altrimenti sarà
783
+ il nome file generato dal server.
784
+ Se *data* è ``None``, la funzione cancella i dati binari associati alla scheda; *force_delete* in questo caso può essere ``True`` se
785
+ si desidera cancellare il riferimento ai dati anche se i dati non sono raggiungibili dal server."""
786
+ return self._execute(CMD_STORE_BINARY, {
787
+ IN_FIELD_NAME: field_or_tablename,
788
+ IN_ROW_ID: id,
789
+ IN_TYPE: type,
790
+ IN_FILENAME: filename,
791
+ IN_ORIGINAL_FILENAME: original_filename,
792
+ IN_CODE_AZIENDA: code_azienda,
793
+ IN_DATA: data,
794
+ IN_DESC: desc,
795
+ IN_FORCE_DELETE: force_delete,
796
+ IN_LABEL: label,
797
+ IN_METADATA: metadata,
798
+ IN_CODE_TIPOLOGIA: code_tipologia,
799
+ }, OUT_FILENAME, progress=progress, log=log)
800
+
801
+ def translate(self, field, value, language, code_azienda=None):
802
+ return self._execute(CMD_TRANSLATE, {
803
+ IN_FIELD: field,
804
+ IN_VALUE: value,
805
+ IN_LANGUAGE: language,
806
+ IN_CODE_AZIENDA: code_azienda,
807
+ }, OUT_TEXT)
808
+
809
+ def set_database_language(self, language, progress=None):
810
+ return self._execute(CMD_SET_DATABASE_LANGUAGE, {
811
+ IN_LANGUAGE: language,
812
+ }, progress=progress)
813
+