Changelog¶
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.