Plugin SDK Python
Il Plugin SDK v1 di XIQUIL e basato su Pluggy e su un loader filesystem. Il contratto pubblico e piccolo e versionato: un plugin installato dichiara cosa offre nel plugin.json, puo esporre route FastAPI, implementare hook di lifecycle e fornire un bundle frontend.
Artefatti pubblici
Sezione intitolata “Artefatti pubblici”Il repository Core contiene i sorgenti canonici degli SDK:
sdk/python, pacchettoxiquil-plugin-sdkcon marker Pluggy,PluginHooks,PluginFacadee type markerpy.typed;sdk/typescript, pacchetto types-only@xiquil/plugin-sdkper il modulo frontend, il contesto host ewindow.__XQ__;sdk/examples, con esempi backend-only, frontend-only e full-stack.
Finche gli artefatti non saranno pubblicati nei registry, usa la versione proveniente dalla release Core corrispondente. Non copiare tipi o marker in un SDK privato: i test Core verificano che il contratto pubblico non derivi dall’implementazione runtime.
Flusso di caricamento
Sezione intitolata “Flusso di caricamento”All’avvio XIQUIL:
- aggiunge
/app/data/plugins/sitealsys.path; - scansiona
/app/data/plugins/cercando sottocartelle conplugin.json; - carica eventuali entry point Python del gruppo
xq-plugins; - filtra i plugin in base a licenza, feature e stato database;
- registra le route abilitate sotto
/api/plugins/{name}e, nella stessa fase, carica eventuali classi hook daplugin.pydei plugin filesystem; - invoca gli hook
on_startup; - espone al frontend il manifest tramite
/api/system/plugins/manifest.
Il frontend usa quel manifest per mostrare le voci di menu e caricare /plugins/{name}/frontend/bundle.js.
Hook disponibili
Sezione intitolata “Hook disponibili”Nei nuovi plugin importa hookimpl dal pacchetto pubblico:
from xiquil_plugin_sdk import PluginFacade, hookimplLo shim storico xq_plugin_sdk resta compatibile con i plugin esistenti, ma non e il punto di ingresso consigliato per nuovo codice.
register_routes
Sezione intitolata “register_routes”Restituisce una lista di APIRouter. Le route vengono montate sotto /api/plugins/{plugin_name}.
@hookimpldef register_routes(self): from . import router return [router.router]Per i plugin filesystem, il core supporta anche il caricamento diretto di router.py. Mantieni comunque register_routes se vuoi compatibilita con plugin installati come entry point.
on_startup
Sezione intitolata “on_startup”Eseguito quando FastAPI avvia l’applicazione e il database engine e disponibile.
@hookimpldef on_startup(self, app, db_engine): from sqlmodel import SQLModel from .models import PluginDemoRecord
SQLModel.metadata.create_all( db_engine, tables=[PluginDemoRecord.__table__], )Usalo per inizializzare risorse e creare tabelle del plugin. Le operazioni devono essere idempotenti: create_all() non aggiunge colonne a tabelle gia esistenti, quindi le migrazioni additive vanno gestite esplicitamente con controlli su colonne e indici.
on_shutdown
Sezione intitolata “on_shutdown”Eseguito in chiusura applicazione.
@hookimpldef on_shutdown(self): passUsalo per rilasciare risorse non gestite automaticamente.
register_nav_items
Sezione intitolata “register_nav_items”Questo hook e presente nella spec Pluggy, ma il core attuale non lo raccoglie con un call site dedicato. Non usarlo come meccanismo di navigazione.
Le voci nel menu laterale vengono costruite dal manifest frontend restituito da /api/system/plugins/manifest. Nei pacchetti pubblicati via Hub, display_name arriva dal form Hub mentre icon, nav_category e nav_path arrivano dal manifest tecnico.
{ "display_name": "Demo Widget", "provides_frontend": true, "icon": "InfoIcon", "nav_category": "sistema", "nav_path": "/plugins/xq_demo_widget"}register_simulation_engine
Sezione intitolata “register_simulation_engine”Hook avanzato per sostituire il motore previsionale del modulo Previsione.
@hookimpldef register_simulation_engine(self): return { "name": "monte_carlo", "run": run_scenarios, }La callable run(context) riceve dati gia preparati dal core e deve restituire una lista compatibile con lo schema degli scenari previsionali. Se nessun plugin implementa questo hook, XIQUIL usa il motore deterministico integrato.
Manifest validato dal core
Sezione intitolata “Manifest validato dal core”Il Core legge sempre il plugin.json installato. Nei pacchetti Marketplace questo file viene generato e firmato dall’Hub a partire da due fonti:
- form Hub per metadata, tier, versione pubblica e compatibilità Core;
plugin.source.jsonper campi tecnici come route, frontend, permessi e pacchetti di sistema.
Durante sviluppo locale puoi usare un plugin.json completo, ma per pubblicare su Marketplace consulta Manifest e metadata plugin.
Schema principale:
| Campo | Tipo | Note |
|---|---|---|
name | string | Obbligatorio, snake_case, pattern ^[a-z][a-z0-9_]*$ |
version | string | Obbligatorio nei pacchetti runtime; per Marketplace viene generato dall’Hub in formato strict x.x.x |
display_name | string | Obbligatorio |
description | string | Obbligatorio |
author | string | Obbligatorio |
required_tier | string | Default pro; valori Hub validi: community, starter, pro, business |
min_core_version | string/null | Versione minima XIQUIL |
max_core_version | string/null | Versione massima XIQUIL |
depends_on | list[string] | Dipendenze da altri plugin |
provides_routes | boolean | Il plugin espone API |
provides_models | boolean | Il plugin crea tabelle proprie |
provides_frontend | boolean | Il plugin include frontend/bundle.js |
icon | string/null | Nome icona MUI |
nav_category | string/null | Categoria menu |
nav_path | string/null | Percorso frontend |
permissions | list[string] | Permessi core dichiarati |
settings_schema | list[dict] | Schema impostazioni plugin |
system_packages | list[object] | Pacchetti apt richiesti |
system_packages usa oggetti con name, optional e description.
enterprise può comparire solo in manifest legacy letti dal Core come alias storico di business; non usarlo nei nuovi plugin e non è accettato dai form Hub.
min_core_version e max_core_version sono controlli bloccanti prima dell’installazione. Usali quando il plugin dipende da API introdotte in una versione specifica del Core o da API che sai essere state rimosse in versioni successive. Nel flusso Marketplace questi valori si impostano nel form Hub e devono usare formato x.x.x.
API plugin
Sezione intitolata “API plugin”Le route di router.py sono FastAPI standard.
from fastapi import APIRouter, Depends
from app.auth.dependencies import LIVELLO_SOLA_LETTURA, require_permission
router = APIRouter()
@router.get("/ping")def ping(): return {"pong": True}
@router.get( "/secure-data", dependencies=[Depends(require_permission("fondi", LIVELLO_SOLA_LETTURA))],)def secure_data(): return {"ok": True}Regole consigliate:
- proteggi ogni route che legge o modifica dati core;
- verifica lo scope per ambito quando lavori su fondi, uscite, entrate, scadenze o anagrafiche;
- usa response model Pydantic/SQLModel dove possibile;
- non esporre endpoint diagnostici con dati sensibili;
- tieni
/pingleggero per smoke test.
Facade Core autorizzata
Sezione intitolata “Facade Core autorizzata”Una route backend del plugin che deve chiamare l’API pubblica del Core puo
richiedere la dipendenza CoreApi. La chiamata viene inoltrata nella stessa
applicazione con il contesto di autenticazione della richiesta corrente: il
Core applica quindi le proprie autorizzazioni e i controlli di ambito.
from fastapi import Dependsfrom xiquil_plugin_sdk import CoreApi, get_core_api
async def usa_api_core( core_api: CoreApi = Depends(get_core_api),): return await core_api.request_json( "GET", "/api/<risorsa>", params={"chiave": "valore"}, )request_json() accetta GET, POST, PUT, PATCH e DELETE; per le
richieste con corpo usa l’argomento json. Il percorso deve iniziare con
/api/ e non puo indirizzare route di plugin, contenere risalite di directory,
query o frammenti: passa gli eventuali parametri con params.
La facade stabilizza il trasporto autorizzato, non gli endpoint o i payload del
Core: scegli risorse e dati compatibili con la versione Core dichiarata dal
manifest. Una risposta 204 restituisce None; gli errori della route Core
mantengono stato e dettaglio HTTP. Non importare servizi di mutazione interni
del Core per aggirare questa API.
Tabelle del plugin
Sezione intitolata “Tabelle del plugin”Le tabelle create da plugin dovrebbero usare un prefisso chiaro:
plugin_{nome_plugin}_{risorsa}Esempio:
plugin_bank_recon_bank_rowsplugin_bank_recon_matchesI plugin non usano Alembic del core. Per aggiornare schema esistente, usa migrazioni idempotenti in on_startup: controlla se colonna o indice esistono, poi applica solo il delta necessario.
Frontend plugin
Sezione intitolata “Frontend plugin”Bundle, ABI TypeScript, PluginModule, PluginContext e globali host sono
documentati nella reference separata Plugin SDK frontend.
Installazione e stati
Sezione intitolata “Installazione e stati”Gli stati principali salvati in database sono:
| Stato | Significato |
|---|---|
installed_pending_restart | Installato, serve restart |
active | Installato, abilitato e caricato |
inactive | Installato ma disabilitato |
update_pending_restart | Aggiornato, serve restart |
uninstall_pending_restart | Rimozione in attesa |
Il tab Sviluppatore nel Marketplace e visibile solo con licenza plugin_developer=true. In sviluppo locale puoi usare PLUGIN_DEVELOPER_MODE=True.
Sicurezza e firma
Sezione intitolata “Sicurezza e firma”I pacchetti Marketplace devono essere firmati e verificati. L’upload manuale in developer mode puo accettare pacchetti unsigned per test locale.
La verifica controlla:
MANIFEST.sha256;MANIFEST.sha256.sig;MANIFEST.sha256.keyidper scegliere la chiave pubblica Marketplace;- firma RSA-PSS del manifest;
- hash SHA-256 dei file elencati.
La chiave pubblica non è hardcoded nel pacchetto: arriva dalla licenza attiva. Il Core usa plugin_pubkeys come trust set per supportare la rotazione delle chiavi Hub; se un pacchetto legacy non contiene MANIFEST.sha256.keyid, usa la chiave attiva plugin_pubkey.
Il catalogo ufficiale mostra solo versioni approvate e firmate dall’Hub. Le versioni beta richiedono una licenza con accesso beta plugin; i plugin di tier superiore possono essere visibili nel Marketplace ma non installabili con una licenza inferiore.
Non usare il developer mode per installare pacchetti di origine non fidata.
Limiti da rispettare
Sezione intitolata “Limiti da rispettare”- Un plugin puo importare codice del core, ma deve trattare i contratti core come API interne soggette a cambiamento.
- Le tabelle plugin devono essere autonome e prefissate.
- Le migrazioni plugin devono essere idempotenti.
- Le route devono applicare permission e scope.
- Il frontend deve usare
window.__XQ__per librerie host. - I pacchetti OS vanno dichiarati nel manifest, non installati a mano.
- Un errore nel plugin viene loggato, ma non deve bloccare il core.