· SOFTWARE

Un'app locale per form e report sul database che hai già

Chi ha un database in produzione e deve dare a qualcuno un modo controllato di inserire dati e tirare fuori report conosce il problema: il database c’è già, lo schema c’è già, e quello che manca è l’interfaccia. Scriverla a mano per ogni tabella è lavoro che non finisce; prendere un generatore di applicazioni significa quasi sempre spostare i dati, o almeno le credenziali, dentro un servizio che gira da un’altra parte.

Visual DB risolve la prima metà del problema in modo pulito, e la sua intuizione è quella che abbiamo preso: davanti a un database esistente servono tre cose, non trenta. Un form per lavorare su un record alla volta, uno sheet per lavorare su molti record come in un foglio di calcolo, un report in sola lettura per leggerli aggregati. Tre pilastri, e sopra quelli si costruisce quasi tutto il lavoro d’ufficio su dati.

Quello che non ci andava bene era il resto: il modello di distribuzione. Da qui è nato dbvisual.

Cosa abbiamo tenuto e cosa abbiamo ribaltato

I tre pilastri sono rimasti quelli. Il modello di esecuzione è l’opposto: l’applicazione si installa e gira sulla macchina di chi la usa. Niente cloud, niente account remoto, niente multi-tenancy — e queste non sono funzioni ancora da fare, sono non-obiettivi dichiarati nella specifica, perché un vincolo scritto è l’unica cosa che regge alla pressione delle richieste successive.

La distinzione che conta è fra l’applicazione e i suoi bersagli. I database di destinazione possono stare dove vogliono, anche su un altro server: è normale puntare a un PostgreSQL aziendale. Ma l’applicazione e i suoi metadati — definizioni, viste salvate, segreti, allegati — restano sempre in locale. Il criterio è BYOD in senso letterale: bring your own database, ci si collega a quello che c’è.

Per un fornitore che lavora su applicazioni medicali, industriali e scientifiche questa non è una preferenza estetica. Quando un dato non può uscire dalla macchina che lo produce, uno strumento che per funzionare chiede di caricarlo altrove non è un’opzione da valutare: è fuori perimetro. Uno strumento locale, invece, si può portare dentro un ambiente controllato e discutere con chi lo deve autorizzare.

Il centro dell’architettura: una specifica, un compilatore

La scelta strutturale che ha ripagato più di tutte è che non esistono due entità distinte “form” e “report”. Esiste una query-spec, in JSON, che dichiara:

  • main_table — la tabella principale, l’unica scrivibile in form e sheet;
  • related[] — le tabelle collegate via chiave esterna, in sola lettura;
  • columns[] — le colonne selezionate, con alias;
  • filters[] — le condizioni, parametrizzate;
  • params[] — i parametri, con supporto multi-valore e a cascata.

Un solo compilatore trasforma la query-spec in una sqlalchemy.select(). Form, sheet e report sono tre render diversi della stessa specifica; tutto il resto è interfaccia.

La conseguenza pratica si vede sulla sicurezza. Se la scrittura è consentita sulla sola main_table e le colonne di lookup sono per costruzione in sola lettura, questa non è una regola da ricordarsi in ogni schermata: è una proprietà del modello, e vale in tutti i render insieme. Stessa cosa per i parametri, che sono sempre bound — non c’è un punto del codice in cui si concatena una stringa SQL, quindi non c’è un punto in cui l’iniezione possa entrare.

L’impianto

Applicazione monolitica: un unico codebase Python, un processo, un eseguibile. Interfaccia con NiceGUI, motore con SQLAlchemy Core 2.0. Lo stesso codice gira come finestra desktop nativa o come web app locale su 127.0.0.1; nessun frontend separato, nessuna build JavaScript.

Tre strati:

  • core — indipendente dal dialetto: creazione dell’engine multi-dialetto e test di connessione, riflessione dello schema (tabelle, colonne, tipi, chiavi esterne), modelli Pydantic della query-spec, compilatore, CRUD generico con master-detail transazionale e locking ottimistico, composizione ed esecuzione del DDL;
  • meta — persistenza locale: store dei metadati su SQLite via platformdirs, segreti cifrati con keyring e fallback su Fernet, archivio locale degli allegati;
  • app — interfaccia e servizi.

I dialetti supportati sono PostgreSQL, MySQL/MariaDB, SQL Server, Oracle, SQLite e DuckDB; i driver sono extra opzionali, si installa solo quello che serve. Sono previsti anche i file locali cifrati con passphrase: SQLite via SQLCipher, e DuckDB con la cifratura nativa da 1.4 in poi. Se il driver SQLCipher non è installato, l’opzione si disabilita con un messaggio esplicito invece di far cadere l’applicazione — che è il comportamento giusto per una dipendenza opzionale.

Sul lato funzioni, quelle che spostano il lavoro reale: locking ottimistico e salvataggio in batch in una sola transazione, colonne calcolate e validazione per campo, master-detail atomico con propagazione della chiave del master ai dettagli nuovi, report con raggruppamento multi-livello e subtotali, viste salvate private/condivise/bloccate, snapshot point-in-time in HTML autoconsistente ed Excel, import/export CSV, webhook non bloccanti su create/update/delete, e row-level security delegata alle policy PostgreSQL — dbvisual passa l’identità corrente con SET app.current_user_email e non reimplementa l’autorizzazione, che è il posto giusto dove non essere creativi.

Due scelte, e il perché

Il DDL non passa dal canale in sola lettura. La scheda Database permette di modificare lo schema, ma ogni modifica compone il DDL e lo mostra in un riquadro “rivedi ed esegui”: l’esecuzione avviene solo dopo conferma esplicita, doppia per le operazioni distruttive. È un canale separato dal ensure_readonly che protegge i report, perché i due hanno requisiti opposti e confonderli è il modo di perdere entrambi.

L’assistente AI è opzionale, spento per default e in sola lettura. Traduce una richiesta in linguaggio naturale in SQL per i report, con la chiave API dell’utente conservata come segreto. L’SQL generato viene sempre mostrato per revisione e validato da ensure_readonly; non viene mai eseguito automaticamente. Un generatore di query non è un motivo per rinunciare al controllo su cosa tocca il database.

Stato e verifica

I test del core girano su SQLite e DuckDB in memoria, quindi non richiedono alcun database esterno né credenziali. I test di integrazione sui dialetti server — PostgreSQL, MySQL/MariaDB, SQL Server, Oracle — sono opt-in: si attivano solo se le variabili d’ambiente puntano a un database reale, altrimenti vengono saltati. Ognuno esercita end-to-end le API che l’applicazione usa davvero: creazione tabella via DDL, riflessione, CRUD con locking ottimistico, rilettura compilata, drop finale.

Il pacchetto standalone si costruisce con nicegui-pack. Metadati, allegati, segreti e snapshot si risolvono sempre attraverso platformdirs, mai dentro la cartella temporanea di PyInstaller: è il difetto classico degli eseguibili confezionati, e si manifesta come dati che scompaiono alla chiusura.

Licenza MIT. La scheda del repository è nell’Open Lab, con il manuale d’uso nella wiki.

← Torna al blog

Un progetto tecnico?

Hardware, firmware, software, acustica: se hai un caso d’uso vicino, parliamone.