Changelog¶
2.2.0 — 2026-06-09¶
UX & UI Improvements
New (Load from DB / Save to DB): Cancel button. Long-running database operations (both table downloads and uploads) can now be aborted mid-flight. For uploads, SQLAlchemy automatically issues a
ROLLBACK, preventing partial or corrupted data from being written.New (Load from DB): Data Preview. When a dataset is selected from the dropdown, the widget now instantly fetches the first 50 rows (
LIMIT 50) and displays them in an embeddedQTableWidget. This provides immediate visual confirmation of the dataset contents without having to pull the entire table into memory first.Improvement (Load from DB): Smooth progress reporting. The background reader now streams data in chunks (
chunksize=1000) rather than running a single monolithicread_sqlquery. The progress bar updates continuously as rows arrive, preventing the UI from appearing frozen during large transfers.UX: Improved button alignment and spacing in both database widgets to avoid visual overlap. In
Save to DB, the Cancel button is stacked logically above the Save button for improved space usage.
2.1.1 — 2026-06-09¶
Load from DB (new widget)
Lists every dataset registered in the
datasetsmetadata table via SQLAlchemy and pulls the selected one back into Orange as anOrange.data.Tableusingpandas.read_sql+Orange.data.pandas_compat.table_from_frame.Offers a Class column combo populated from the dataset’s columns, pre-selected with the user’s persisted choice or the
class_namerecorded by Save to DB. The outputTablealready exposes the chosen column asdomain.class_var, so no Select Columns widget is needed downstream.Same dialect selector (PostgreSQL / MySQL), connection-status label and
QThreadworker pattern as Save to DB. Both the metadata listing and the actual table read happen off the GUI thread.Workflow-persisted settings:
selected_datasetandselected_classareSetting(..., schema_only=True), restored as soon as the connection comes back up on reload.The Dataset combo is a
ComboBoxSearch— type to filter when the registry gets large.↻button next to the Dataset combo re-runs the listing without dropping the connection (handy when something else just published a new dataset). Auto-load is suppressed on Refresh so the user never gets a Load they didn’t ask for.Deletebutton drops the selected dataset’s table and removes its row indatasets. Runs through a_DeleteDatasetWorkeron a background thread, guarded by a confirmation dialog. Idempotent: re-deleting a missing table is a no-op thanks toDROP TABLE IF EXISTS.Auto-load on workflow reopen: if the persisted
selected_datasetis still on the server when the connection succeeds and the listing returns, Load fires automatically — the data flows out without a single click. The flag is cleared on Refresh and on Delete to avoid surprises.
Variable Dependency Graph
New: edges now carry numeric weights equal to the largest
|argument|among the temporal calls (shift,sum,mean,count,min,max,sd) in the source expression that reference the dependency. Plain (non-temporal) references default to weight1. The weights live innetwork.edges[0].edges.dataand are consumable by any downstream Network widget.New: the graph is now directed (
DirectedEdges). Previously the sparse matrix was passed straight toNetworkwhich auto-wrapped it asUndirectedEdges— a latent bug since A→B is not the same as B→A in a dependency graph.New: nodes carry an extra
expressionmeta (literal expression text, empty for original variables) so the Network Explorer can show the formula as the node label.New:
Warning.no_derivedfires when every input row is an original variable — usually a sign the user wired the data output of Time Features Constructor instead of the variable-definitions one.Refactor: flattened the
from_row_col/grafodecorator pattern into a single, documentedbuild_dependency_networkfunction.Performance: O(n³) → O(n²) by precomputing a
name → indexmap and using asetto dedupe dependencies.Bugfix: an
IndexErrorwas raised when the input domain had a single column. The widget now checkslen(domain) >= 2before reading the second column.Removed 65 lines of dead commented-out code.
Time Features Constructor
New: chained descriptors. A descriptor can now reference another derived descriptor in its expression (e.g.
X2 := shift(X1, -1)withX1itself defined a few rows above). The widget topologically sorts the descriptors and cascades the transforms — each step runs against the table state produced by the previous one, soX2seesX1as a regular column. Cycles (e.g.X1 := X2 + 1together withX2 := X1 + 1) raise a Circular dependency between descriptors: X1, X2 error. Errors during evaluation are reported per-descriptor so the failing row is obvious.Fixed (critical): time-window functions used to lose context every 5 000 rows because Orange chunks tables during
transform. The widget now caches the full source perFeatureFuncand returns the right slice for each chunk, soshift(x, -20)is correct across chunk boundaries on datasets of any size.Fixed: “Variables to generate” was being cleared after every Send and on every input change, so workflow save / reload lost the definitions. Storage moved from
ContextSettingtoSetting(..., schema_only=True)(matching upstream Orange v4) and the editor is now restored from the persisted descriptors on every input. The legacy context handler stays around to migrate old workflows.Each Send re-transforms the original input table instead of the cumulative output, removing the implicit “consume on Send” semantics and the duplicate-name errors that came with it.
evalhardened:__builtins__replaced with an empty dict in the expression evaluator. Only the curated whitelist (safe builtins +math+ selectedrandom/numpyhelpers) is exposed.Refactor:
modificar_expressioncollapsed from 7 near-identical loops to a single regex-driven pass.Bugfix:
FeatureEditor.editorDatareturned the variable name as the expression.
Save to DB
New: write mode selector with three options — Create new (default, fail if the target exists), Overwrite (drop and recreate the table and its
datasetsrow), Append (keep existing rows and add the new ones). Re-running a workflow no longer breaks. The persistedwrite_modeSetting defaults to"create"so old workflows keep their previous behaviour. After the upload, the widget runsSELECT COUNT(*)and rewrites thedatasetsrow with the actual total, so the registry stays accurate across appends.New: MySQL support. The connection panel now exposes a database-type selector (PostgreSQL / MySQL). Per-dialect column types and identifier quoting (
"name"vs\`name\`) live in a_Dialectabstraction.Performance: uploads now go through
Orange.data.pandas_compat.table_to_frame+DataFrame.to_sql(method='multi', chunksize=1000)over a SQLAlchemy engine. The old row-per-INSERT loop is gone — typical speedups are 50-100×, especially over the network. Identifier quoting and per-column DDL types are emitted by the SQLAlchemy dialect (DOUBLE_PRECISIONon PostgreSQL,DOUBLEon MySQL,DATETIMEinstead ofTIMESTAMPon MySQL to dodge the 2038 cap,VARCHAR(255)/TEXTeverywhere else).UX: the upload runs in a background
QThread(_UploadWorker), so the canvas stays interactive on long writes. A status label under the connection box reports Not connected / Connected to … : … / Uploading rows N/M… / Upload completed in Xs / Upload failed: … with colour cues. Save, Connect and the form fields are disabled while the worker runs and re-enabled on success or failure; the widget aborts the thread cleanly on close.Fixed (critical): SQL injection in the metadata
INSERT. The query is now fully parametrised.Added an identifier whitelist (
^[A-Za-z_][A-Za-z0-9_]{0,62}$) and aquote_identhelper that wraps identifiers with PostgreSQL-standard double quotes (with any internal"doubled). Used in everyCREATE TABLE/INSERT INTOthat touches user-supplied names.Replaced
psycopg2withpsycopg2-binaryin the install requirements so installation works on macOS, Linux and Windows without a C compiler.setup.pynow declaresinstall_requires;pip installwas previously not pulling in any runtime dependency.
Testing¶
The widget suite grew to 92 tests, covering:
Unit tests for
modificar_expression, the time-window helpers,_sanitize_name/_expression_or_noneand_temporal_weights.A regression test for the 5 000-row chunk bug (
shift(x, -20)over a 12 000-row table).eval-safety tests asserting that__import__/openraiseNameErrorwhilesqrt/absstill resolve.Widget-level tests (via
Orange.widgets.tests.base.WidgetTest) for descriptor persistence, including an end-to-endsettingsHandler.pack_data/stored_settings=round-trip that mirrors what Orange does when saving and reopening a.owsfile.Edge-weight tests covering single calls, three-argument families, mixed temporal / non-temporal references, max across multiple calls, and per-dependency independence.