Changelog

2.3.0

Fixes

  • Fix (Time Features Constructor): time-window functions accept spaces between arguments (shift(x, -1), mean(x, -2, 0)) and a leading + on offsets (shift(x,+1)). Previously these raised NameError or TypeError. min(, max(, mean( and sum( are no longer matched inside longer names such as nanmin(.

  • Fix (Time Features Constructor): columns whose names contain punctuation other than spaces or hyphens (a.b, temp (C)) or start with a digit (1abc) can be referenced by their sanitised name (a_b, temp__C_, _1abc), as the documentation already stated. Previously they raised NameError.

  • Fix (Time Features Constructor): expressions that reference no column (constants such as 42, and invalid expressions that the widget blanks out) yield one value per row instead of failing with could not broadcast input array.

  • Fix (Variable Dependency Graph): variable names are sanitised with the constructor’s sanitized_name, so references to columns with punctuation or a leading digit now produce edges. Node names for those columns change accordingly (temp (C) → temp__C_).

  • Fix (Variable Dependency Graph): references and temporal weights are read from the expression’s syntax tree instead of searching its text. Names inside string literals, names bound in comprehensions or lambdas, and called function names (e.g. a column called sum) no longer create edges, and a temporal call only counts in the form the constructor evaluates. Expressions that cannot be parsed get no edges and a new warning names them.

  • Fix (Save to DB): Overwrite no longer drops the existing table before uploading. The data goes into a staging table that replaces the target only once every chunk is in (atomic RENAME TABLE on MySQL). Previously, on MySQL, a failed or cancelled Overwrite lost the original table because DDL auto-commits. The same staging path means a failed or cancelled Create (or Append into a missing table) no longer leaves a half-written table behind, and Create now also refuses a table that exists without a datasets row.

  • Fix (Load from DB): each background operation keeps its own thread. Previously the listing thread, on finishing, cleared the references of the load it had just started (auto-load on workflow reopen, or Load clicked right after listing): Cancel stopped working, and closing the widget could destroy the still-running thread and abort Orange (QThread: Destroyed while thread is still running).

  • Fix (Load from DB / Save to DB): removing the widget or closing the workflow mid-operation no longer freezes the canvas waiting for the query or upload to end; the operation is cancelled and its thread ends on its own. On exit, Orange waits for any such thread to finish.

  • Improvement (Load from DB): loads read through a server-side cursor (stream_results), so rows really arrive in 1 000-row chunks: the progress bar is accurate and Cancel takes effect within one chunk instead of after the whole table has been fetched. Cancel is no longer enabled while a dataset is being deleted.

  • Tests (Save to DB): the upload worker is tested end to end on SQLite and, when TIMEFEATURES_TEST_POSTGRES_URL / TIMEFEATURES_TEST_MYSQL_URL point to disposable databases, on real PostgreSQL and MySQL servers, including failures and cancellations mid-upload.

Packaging & CI

  • setup.py now declares Orange3>=3.36.2 and python_requires=">=3.10", the oldest combination the tests pass on (older Orange3 releases do not work with NumPy 2). Previously Orange3 only arrived indirectly through Orange3-Network, and the README advertised Python 3.8+.

  • New GitHub Actions workflow (.github/workflows/ci.yml): the test suite on Python 3.10 with Orange3 3.36.2 and on Python 3.11 and 3.12 with the latest releases, with the Save to DB tests also running against PostgreSQL and MySQL service containers; a package job that builds the sdist and wheel, runs twine check and installs the wheel into an empty environment to check the entry points, icons and bundled help; and a docs job that builds Sphinx with warnings as errors.

  • Removed setup.cfg: its only key (description-file) is deprecated and will stop being accepted by setuptools; setup.py already sets the long description.

  • The license is declared as the SPDX expression GPL-3.0-or-later instead of the deprecated license classifier, and packages are discovered with find_namespace_packages so setuptools keeps shipping the bundled help and icons without warnings. The built wheel has exactly the same files as before.

  • Automatic releases: after a merge to main, once the tests, package and docs jobs pass, the workflow publishes the version in timefeatures/__version__.py to PyPI if it is not there yet (PyPI trusted publishing, no stored token) and creates the matching vX.Y.Z tag and GitHub release. See RELEASING.md.

Documentation

  • The README and the constructor page no longer describe the restricted eval as a secure sandbox; they warn to open workflows only from trusted sources.

  • Removed the completion-email references from the README, the index and the Save to DB page: notifications are currently disabled.

  • Rebuilt the bundled in-app help (timefeatures/help_html) so the Orange Help window shows the updated pages.

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 embedded QTableWidget. 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 monolithic read_sql query. 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

Load from DB (new widget)

  • Lists every dataset registered in the datasets metadata table via SQLAlchemy and pulls the selected one back into Orange as an Orange.data.Table using pandas.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_name recorded by Save to DB. The output Table already exposes the chosen column as domain.class_var, so no Select Columns widget is needed downstream.

  • Same dialect selector (PostgreSQL / MySQL), connection-status label and QThread worker pattern as Save to DB. Both the metadata listing and the actual table read happen off the GUI thread.

  • Workflow-persisted settings: selected_dataset and selected_class are Setting(..., 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.

  • Delete button drops the selected dataset’s table and removes its row in datasets. Runs through a _DeleteDatasetWorker on a background thread, guarded by a confirmation dialog. Idempotent: re-deleting a missing table is a no-op thanks to DROP TABLE IF EXISTS.

  • Auto-load on workflow reopen: if the persisted selected_dataset is 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 weight 1. The weights live in network.edges[0].edges.data and are consumable by any downstream Network widget.

  • New: the graph is now directed (DirectedEdges). Previously the sparse matrix was passed straight to Network which auto-wrapped it as UndirectedEdges — a latent bug since A→B is not the same as B→A in a dependency graph.

  • New: nodes carry an extra expression meta (literal expression text, empty for original variables) so the Network Explorer can show the formula as the node label.

  • New: Warning.no_derived fires 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 / grafo decorator pattern into a single, documented build_dependency_network function.

  • Performance: O(n³) → O(n²) by precomputing a name → index map and using a set to dedupe dependencies.

  • Bugfix: an IndexError was raised when the input domain had a single column. The widget now checks len(domain) >= 2 before 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) with X1 itself 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, so X2 sees X1 as a regular column. Cycles (e.g. X1 := X2 + 1 together with X2 := 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 per FeatureFunc and returns the right slice for each chunk, so shift(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 ContextSetting to Setting(..., 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.

  • eval hardened: __builtins__ replaced with an empty dict in the expression evaluator. Only the curated whitelist (safe builtins + math + selected random / numpy helpers) is exposed.

  • Refactor: modificar_expression collapsed from 7 near-identical loops to a single regex-driven pass.

  • Bugfix: FeatureEditor.editorData returned 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 datasets row), Append (keep existing rows and add the new ones). Re-running a workflow no longer breaks. The persisted write_mode Setting defaults to "create" so old workflows keep their previous behaviour. After the upload, the widget runs SELECT COUNT(*) and rewrites the datasets row 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 _Dialect abstraction.

  • 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_PRECISION on PostgreSQL, DOUBLE on MySQL, DATETIME instead of TIMESTAMP on MySQL to dodge the 2038 cap, VARCHAR(255) / TEXT everywhere 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 a quote_ident helper that wraps identifiers with PostgreSQL-standard double quotes (with any internal " doubled). Used in every CREATE TABLE / INSERT INTO that touches user-supplied names.

  • Replaced psycopg2 with psycopg2-binary in the install requirements so installation works on macOS, Linux and Windows without a C compiler.

  • setup.py now declares install_requires; pip install was 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_none and _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__ / open raise NameError while sqrt / abs still resolve.

  • Widget-level tests (via Orange.widgets.tests.base.WidgetTest) for descriptor persistence, including an end-to-end settingsHandler.pack_data / stored_settings= round-trip that mirrors what Orange does when saving and reopening a .ows file.

  • Edge-weight tests covering single calls, three-argument families, mixed temporal / non-temporal references, max across multiple calls, and per-dependency independence.