TimeFeatures 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {timefeatures-2.0.0/TimeFeatures.egg-info → timefeatures-2.1.0}/PKG-INFO +5 -2
  2. {timefeatures-2.0.0 → timefeatures-2.1.0}/README.md +4 -1
  3. {timefeatures-2.0.0 → timefeatures-2.1.0/TimeFeatures.egg-info}/PKG-INFO +5 -2
  4. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/changes.rst +46 -0
  5. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/widgets/load-from-db.rst +40 -4
  6. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/widgets/save-to-db.rst +41 -6
  7. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/widgets/time-feature-constructor.rst +27 -0
  8. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/widgets/variable-dependency-graph.rst +17 -3
  9. {timefeatures-2.0.0 → timefeatures-2.1.0}/setup.py +1 -1
  10. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/changes.rst.txt +46 -0
  11. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/widgets/load-from-db.rst.txt +40 -4
  12. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/widgets/save-to-db.rst.txt +41 -6
  13. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/widgets/time-feature-constructor.rst.txt +27 -0
  14. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/widgets/variable-dependency-graph.rst.txt +17 -3
  15. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/changes.html +46 -0
  16. timefeatures-2.1.0/timefeatures/help_html/objects.inv +0 -0
  17. timefeatures-2.1.0/timefeatures/help_html/searchindex.js +1 -0
  18. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/widgets/load-from-db.html +41 -4
  19. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/widgets/save-to-db.html +45 -7
  20. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/widgets/time-feature-constructor.html +25 -0
  21. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/widgets/variable-dependency-graph.html +19 -3
  22. timefeatures-2.1.0/timefeatures/widgets/icons/loaddatadb.svg +1 -0
  23. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/owloadfromdb.py +206 -7
  24. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/owsavetodb.py +182 -146
  25. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/owtimefeaturesconstructor.py +141 -26
  26. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/owvardependencygraph.py +74 -34
  27. timefeatures-2.0.0/timefeatures/help_html/objects.inv +0 -0
  28. timefeatures-2.0.0/timefeatures/help_html/searchindex.js +0 -1
  29. timefeatures-2.0.0/timefeatures/widgets/icons/loaddatadb.svg +0 -1
  30. {timefeatures-2.0.0 → timefeatures-2.1.0}/LICENSE +0 -0
  31. {timefeatures-2.0.0 → timefeatures-2.1.0}/MANIFEST.in +0 -0
  32. {timefeatures-2.0.0 → timefeatures-2.1.0}/TimeFeatures.egg-info/SOURCES.txt +0 -0
  33. {timefeatures-2.0.0 → timefeatures-2.1.0}/TimeFeatures.egg-info/dependency_links.txt +0 -0
  34. {timefeatures-2.0.0 → timefeatures-2.1.0}/TimeFeatures.egg-info/entry_points.txt +0 -0
  35. {timefeatures-2.0.0 → timefeatures-2.1.0}/TimeFeatures.egg-info/requires.txt +0 -0
  36. {timefeatures-2.0.0 → timefeatures-2.1.0}/TimeFeatures.egg-info/top_level.txt +0 -0
  37. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/Makefile +0 -0
  38. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/conf.py +0 -0
  39. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/index.rst +0 -0
  40. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/installation.rst +0 -0
  41. {timefeatures-2.0.0 → timefeatures-2.1.0}/docs/requirements.txt +0 -0
  42. {timefeatures-2.0.0 → timefeatures-2.1.0}/imgs/installation.png +0 -0
  43. {timefeatures-2.0.0 → timefeatures-2.1.0}/imgs/workflow.png +0 -0
  44. {timefeatures-2.0.0 → timefeatures-2.1.0}/setup.cfg +0 -0
  45. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/__init__.py +0 -0
  46. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help.py +0 -0
  47. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/.buildinfo +0 -0
  48. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/index.rst.txt +0 -0
  49. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_sources/installation.rst.txt +0 -0
  50. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/alabaster.css +0 -0
  51. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/basic.css +0 -0
  52. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/custom.css +0 -0
  53. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/doctools.js +0 -0
  54. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/documentation_options.js +0 -0
  55. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/file.png +0 -0
  56. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/github-banner.svg +0 -0
  57. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/language_data.js +0 -0
  58. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/minus.png +0 -0
  59. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/plus.png +0 -0
  60. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/pygments.css +0 -0
  61. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/searchtools.js +0 -0
  62. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/_static/sphinx_highlight.js +0 -0
  63. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/genindex.html +0 -0
  64. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/index.html +0 -0
  65. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/installation.html +0 -0
  66. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/help_html/search.html +0 -0
  67. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/__init__.py +0 -0
  68. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/icons/graphgenerator.svg +0 -0
  69. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/icons/savedatadb.svg +0 -0
  70. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/icons/timefeature-xs.svg +0 -0
  71. {timefeatures-2.0.0 → timefeatures-2.1.0}/timefeatures/widgets/icons/timefeature.svg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TimeFeatures
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Timefeatures add-on for Orange 3 data mining software.
5
5
  Home-page: https://github.com/alervgr/Orange-TimeFeatures
6
6
  Author: Alejandro Rivas García
@@ -86,12 +86,15 @@ If using Anaconda Python distribution, simply run
86
86
  pip install TimeFeatures
87
87
 
88
88
  **Required Dependencies**:
89
+
89
90
  * numpy>=1.22.4
90
91
  * AnyQt>=0.2.0
91
- * Orange3>=3.34.0
92
92
  * PyQt5>=5.15.6
93
93
  * PyQtWebEngine>=5.15.6
94
94
  * scipy>=1.7.3
95
+ * SQLAlchemy>=1.4.0
96
+ * psycopg2-binary>=2.9.9
97
+ * PyMySQL>=1.0.0
95
98
  * Orange3-Network>=1.8.0
96
99
 
97
100
  Usage
@@ -42,12 +42,15 @@ If using Anaconda Python distribution, simply run
42
42
  pip install TimeFeatures
43
43
 
44
44
  **Required Dependencies**:
45
+
45
46
  * numpy>=1.22.4
46
47
  * AnyQt>=0.2.0
47
- * Orange3>=3.34.0
48
48
  * PyQt5>=5.15.6
49
49
  * PyQtWebEngine>=5.15.6
50
50
  * scipy>=1.7.3
51
+ * SQLAlchemy>=1.4.0
52
+ * psycopg2-binary>=2.9.9
53
+ * PyMySQL>=1.0.0
51
54
  * Orange3-Network>=1.8.0
52
55
 
53
56
  Usage
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TimeFeatures
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Timefeatures add-on for Orange 3 data mining software.
5
5
  Home-page: https://github.com/alervgr/Orange-TimeFeatures
6
6
  Author: Alejandro Rivas García
@@ -86,12 +86,15 @@ If using Anaconda Python distribution, simply run
86
86
  pip install TimeFeatures
87
87
 
88
88
  **Required Dependencies**:
89
+
89
90
  * numpy>=1.22.4
90
91
  * AnyQt>=0.2.0
91
- * Orange3>=3.34.0
92
92
  * PyQt5>=5.15.6
93
93
  * PyQtWebEngine>=5.15.6
94
94
  * scipy>=1.7.3
95
+ * SQLAlchemy>=1.4.0
96
+ * psycopg2-binary>=2.9.9
97
+ * PyMySQL>=1.0.0
95
98
  * Orange3-Network>=1.8.0
96
99
 
97
100
  Usage
@@ -22,6 +22,22 @@ Unreleased
22
22
  - Workflow-persisted settings: ``selected_dataset`` and
23
23
  ``selected_class`` are ``Setting(..., schema_only=True)``, restored
24
24
  as soon as the connection comes back up on reload.
25
+ - The Dataset combo is a ``ComboBoxSearch`` — type to filter when the
26
+ registry gets large.
27
+ - ``↻`` button next to the Dataset combo re-runs the listing without
28
+ dropping the connection (handy when something else just published a
29
+ new dataset). Auto-load is suppressed on Refresh so the user never
30
+ gets a Load they didn't ask for.
31
+ - ``Delete`` button drops the selected dataset's table and removes
32
+ its row in ``datasets``. Runs through a ``_DeleteDatasetWorker`` on
33
+ a background thread, guarded by a confirmation dialog. Idempotent:
34
+ re-deleting a missing table is a no-op thanks to
35
+ ``DROP TABLE IF EXISTS``.
36
+ - **Auto-load** on workflow reopen: if the persisted
37
+ ``selected_dataset`` is still on the server when the connection
38
+ succeeds and the listing returns, Load fires automatically — the
39
+ data flows out without a single click. The flag is cleared on
40
+ Refresh and on Delete to avoid surprises.
25
41
 
26
42
  **Variable Dependency Graph**
27
43
 
@@ -31,6 +47,17 @@ Unreleased
31
47
  reference the dependency. Plain (non-temporal) references default to
32
48
  weight ``1``. The weights live in ``network.edges[0].edges.data``
33
49
  and are consumable by any downstream Network widget.
50
+ - *New:* the graph is now **directed** (``DirectedEdges``). Previously
51
+ the sparse matrix was passed straight to ``Network`` which auto-wrapped
52
+ it as ``UndirectedEdges`` — a latent bug since A→B is not the same as
53
+ B→A in a dependency graph.
54
+ - *New:* nodes carry an extra ``expression`` meta (literal expression
55
+ text, empty for original variables) so the Network Explorer can show
56
+ the formula as the node label.
57
+ - *New:* ``Warning.no_derived`` fires when every input row is an
58
+ original variable — usually a sign the user wired the data output
59
+ of Time Features Constructor instead of the variable-definitions
60
+ one.
34
61
  - Refactor: flattened the ``from_row_col`` / ``grafo`` decorator pattern
35
62
  into a single, documented ``build_dependency_network`` function.
36
63
  - Performance: O(n³) → O(n²) by precomputing a ``name → index`` map and
@@ -42,6 +69,16 @@ Unreleased
42
69
 
43
70
  **Time Features Constructor**
44
71
 
72
+ - *New:* **chained descriptors**. A descriptor can now reference
73
+ another derived descriptor in its expression (e.g. ``X2 :=
74
+ shift(X1, -1)`` with ``X1`` itself defined a few rows above). The
75
+ widget topologically sorts the descriptors and cascades the
76
+ transforms — each step runs against the table state produced by
77
+ the previous one, so ``X2`` sees ``X1`` as a regular column.
78
+ Cycles (e.g. ``X1 := X2 + 1`` together with ``X2 := X1 + 1``) raise
79
+ a *Circular dependency between descriptors: X1, X2* error. Errors
80
+ during evaluation are reported per-descriptor so the failing row is
81
+ obvious.
45
82
  - *Fixed (critical):* time-window functions used to lose context every
46
83
  5 000 rows because Orange chunks tables during ``transform``. The
47
84
  widget now caches the full source per ``FeatureFunc`` and returns the
@@ -68,6 +105,15 @@ Unreleased
68
105
 
69
106
  **Save to DB**
70
107
 
108
+ - *New:* **write mode** selector with three options — *Create new*
109
+ (default, fail if the target exists), *Overwrite* (drop and
110
+ recreate the table and its ``datasets`` row), *Append* (keep
111
+ existing rows and add the new ones). Re-running a workflow no
112
+ longer breaks. The persisted ``write_mode`` Setting defaults to
113
+ ``"create"`` so old workflows keep their previous behaviour. After
114
+ the upload, the widget runs ``SELECT COUNT(*)`` and rewrites the
115
+ ``datasets`` row with the actual total, so the registry stays
116
+ accurate across appends.
71
117
  - *New:* **MySQL support**. The connection panel now exposes a
72
118
  database-type selector (PostgreSQL / MySQL). Per-dialect column
73
119
  types and identifier quoting (``"name"`` vs ``\`name\```) live in
@@ -48,9 +48,21 @@ Controls
48
48
  host/db (N datasets)", "Loaded <name> (N rows)"), error
49
49
  ("Connection failed: …", "Load failed: …").
50
50
  * - Dataset
51
- - Combo populated from ``SELECT * FROM datasets ORDER BY
52
- datetime DESC``. The most recent upload comes first; the last
53
- choice is restored when reopening a workflow.
51
+ - Searchable combo populated from ``SELECT * FROM datasets ORDER
52
+ BY datetime DESC``. The most recent upload comes first; the
53
+ last choice is restored when reopening a workflow. Type to
54
+ filter the visible items — handy when the registry grows large.
55
+ * - ↻ (Refresh)
56
+ - Small button to the right of the Dataset combo. Re-runs the
57
+ list query without dropping the connection. Useful if another
58
+ Orange canvas (or a parallel tool) just published a new
59
+ dataset while this widget was open.
60
+ * - Delete
61
+ - Drops the currently selected dataset's table from the database
62
+ and removes its row in ``datasets``. Gated by a confirmation
63
+ dialog — there is no Orange-side undo. The operation runs on a
64
+ background thread, so the canvas stays responsive even if the
65
+ table is large.
54
66
  * - Dataset info
55
67
  - Read-only block under the combo: save timestamp, row/column
56
68
  counts and the original class column recorded by **Save to DB**.
@@ -60,7 +72,9 @@ Controls
60
72
  choice, (2) the ``class_name`` stored in the ``datasets``
61
73
  metadata, (3) ``(no class)`` if none of the above apply.
62
74
  * - Load
63
- - Triggers the actual download.
75
+ - Triggers the actual download. Skipped automatically on the
76
+ first connection after a workflow reopen if the persisted
77
+ dataset name is still available (see *Auto-load* below).
64
78
 
65
79
  How it Works
66
80
  ------------
@@ -88,6 +102,28 @@ While any worker runs, the form controls (database type, connection
88
102
  fields, **Connect**, **Load**, dataset and class combos) are
89
103
  temporarily disabled and the status label keeps the user informed.
90
104
 
105
+ Auto-load
106
+ ---------
107
+
108
+ When you reopen a workflow that already had a Load from DB widget with
109
+ a saved ``selected_dataset``, the widget fires Load automatically the
110
+ first time the dataset listing comes back successfully. The data flows
111
+ out of the **Data** output without a single click, mirroring how Orange
112
+ treats sources like **File** and **Datasets**.
113
+
114
+ Subtleties:
115
+
116
+ - Auto-load is a *one-shot* per widget lifetime. Manually clicking
117
+ **Refresh** clears the pending flag, so a Refresh never surprises
118
+ the user by loading something behind their back.
119
+ - If the persisted dataset no longer exists on the server (deleted
120
+ from outside, or the registry was wiped), the flag is cleared and
121
+ the widget just shows the available list — no error.
122
+ - If a different backend or set of credentials is restored, the user
123
+ still has to click **Connect** explicitly, just like in earlier
124
+ versions; auto-load happens *after* the first successful connection
125
+ + listing.
126
+
91
127
  Workflow Persistence
92
128
  --------------------
93
129
 
@@ -59,6 +59,21 @@ TimeFeatures-specific controls:
59
59
  - Destination table name. Validated against PostgreSQL identifier
60
60
  rules (see *Validation* below); MySQL accepts a superset, so
61
61
  the same rule is safe on both.
62
+ * - Mode
63
+ - Combo between the Table name and the Email fields:
64
+
65
+ - **Create new (fail if table exists)** — default, refuses to
66
+ touch an existing table or metadata row. Use this for first
67
+ uploads or when you want a clear error on accidental name
68
+ collisions.
69
+ - **Overwrite (drop and recreate)** — drops the existing data
70
+ table and the matching ``datasets`` row before uploading,
71
+ then recreates them. Re-running the workflow stops breaking.
72
+ - **Append (keep existing rows)** — adds rows to the existing
73
+ table (creating it if it doesn't exist). After the upload,
74
+ the widget runs ``SELECT COUNT(*)`` and rewrites the
75
+ ``datasets`` row with the *actual* total row count, so the
76
+ registry stays accurate across repeated appends.
62
77
  * - Email
63
78
  - Optional notification address. A summary email is sent once the
64
79
  upload finishes, including the table name, row / column counts,
@@ -79,12 +94,32 @@ When **Save** is clicked, the widget:
79
94
 
80
95
  - converts the dataset to a pandas DataFrame with
81
96
  ``Orange.data.pandas_compat.table_to_frame(include_metas=True)``
82
- and reorders the columns (class first, then metas in domain order,
83
- then attributes) to match the existing schema convention;
84
- - creates the ``datasets`` metadata table if it doesn't exist;
85
- - inserts one row into ``datasets`` describing this upload;
86
- - writes the dataset itself in ``PANDAS_SQL_CHUNKSIZE`` chunks
87
- (default ``1 000``) via ``df.to_sql(..., method='multi')``.
97
+ and reorders the columns (class first, then metas in domain
98
+ order, then attributes) to match the existing schema convention;
99
+ - ensures the ``datasets`` metadata table exists;
100
+ - applies the mode-specific preparation:
101
+
102
+ - **create** fail fast if a ``datasets`` row with this name
103
+ already exists.
104
+ - **overwrite** — ``DROP TABLE IF EXISTS`` on the target plus
105
+ ``DELETE`` of the matching ``datasets`` row.
106
+ - **append** — leave everything in place; the upload itself will
107
+ create the table on the first chunk if it doesn't exist.
108
+
109
+ - writes the dataset in ``PANDAS_SQL_CHUNKSIZE`` chunks (default
110
+ ``1 000``) via ``df.to_sql(..., method='multi')`` with the
111
+ ``if_exists`` value tailored to the mode (see
112
+ ``_pandas_if_exists`` for the exact table);
113
+ - runs ``SELECT COUNT(*)`` on the target table and re-inserts the
114
+ ``datasets`` row with that real row count, so the registry stays
115
+ consistent even after multiple appends.
116
+
117
+ Everything happens inside a single SQLAlchemy transaction
118
+ (``engine.begin()``), so a mid-upload error rolls every step back on
119
+ PostgreSQL. MySQL auto-commits DDL (``DROP TABLE``, ``CREATE TABLE``),
120
+ so an Overwrite that crashes during the upload may leave you without
121
+ the original table — there's no way around that without engine-level
122
+ support.
88
123
 
89
124
  While the worker runs, the widget's progress bar and status label are
90
125
  updated through Qt signals; the **Save**, **Connect** and form controls
@@ -144,6 +144,33 @@ returns the appropriate slice for each chunk, so a call like
144
144
  ``shift(x, -20)`` keeps the right value across chunk boundaries even on
145
145
  multi-million-row tables.
146
146
 
147
+ Chained descriptors
148
+ -------------------
149
+
150
+ Descriptors may reference each other. For example:
151
+
152
+ .. code-block:: python
153
+
154
+ X1 := shift(price, -1)
155
+ X2 := X1 + bias
156
+
157
+ When this happens, the widget topologically sorts the descriptors by
158
+ their dependencies and applies them in cascade — each transform step
159
+ runs against the table state produced by the previous step, so ``X2``
160
+ sees ``X1`` as if it were a regular source column.
161
+
162
+ - The order you click **New** in does not matter. Define ``X2`` before
163
+ ``X1`` and the cascade still works.
164
+ - Cycles are detected: writing ``X1 := X2 + 1`` together with
165
+ ``X2 := X1 + 1`` raises a *Circular dependency between descriptors:
166
+ X1, X2* error instead of producing garbage.
167
+ - Per-descriptor error reporting: if one expression fails (e.g.
168
+ ``shift(unknown, -1)``), the error mentions the descriptor name so
169
+ you know which row to fix.
170
+ - Time-window correctness is preserved through the chain: a chained
171
+ ``X2`` that reads ``X1`` over a window keeps producing the right
172
+ values even past Orange's 5 000-row chunk boundary.
173
+
147
174
  Workflow persistence
148
175
  --------------------
149
176
 
@@ -33,9 +33,9 @@ Outputs
33
33
  - Description
34
34
  * - Network
35
35
  - ``orangecontrib.network.Network``
36
- - A directed weighted graph. ``network.edges[0].edges`` is the
37
- sparse adjacency matrix; its non-zero values carry the edge
38
- weights described below.
36
+ - A **directed** weighted graph. ``network.edges[0]`` is a
37
+ ``DirectedEdges`` instance whose sparse adjacency matrix carries
38
+ the per-edge weights described in *Edge Weights* below.
39
39
 
40
40
  How It Works
41
41
  ------------
@@ -118,6 +118,11 @@ downstream styling:
118
118
  * - ``var_type``
119
119
  - Discrete
120
120
  - ``Derived`` (has an expression) or ``Original`` (source feature).
121
+ * - ``expression``
122
+ - String
123
+ - The literal expression text for derived variables; empty for
124
+ original ones. Pick it as **Label** in Network Explorer to see
125
+ each derived node's formula directly on the graph.
121
126
 
122
127
  Controls
123
128
  --------
@@ -126,6 +131,15 @@ Controls
126
131
  table. The widget also auto-regenerates whenever a valid input
127
132
  arrives.
128
133
 
134
+ Warnings
135
+ --------
136
+
137
+ - *Input has no derived variables; the dependency graph is empty.*
138
+ Fires when every row of the configuration table is an original
139
+ variable (no ``Expression`` set). The output network has nodes but
140
+ no edges. Usually means you forgot to attach the second output of
141
+ **Time Features Constructor** instead of the data output.
142
+
129
143
  Input Requirements
130
144
  ------------------
131
145
 
@@ -41,7 +41,7 @@ setup(name="TimeFeatures",
41
41
  "Sphinx>=7.0",
42
42
  ],
43
43
  },
44
- version="2.0.0",
44
+ version="2.1.0",
45
45
  author="Alejandro Rivas García",
46
46
  author_email="alejandrorivasgarcia@gmail.com",
47
47
  keywords=[
@@ -22,6 +22,22 @@ Unreleased
22
22
  - Workflow-persisted settings: ``selected_dataset`` and
23
23
  ``selected_class`` are ``Setting(..., schema_only=True)``, restored
24
24
  as soon as the connection comes back up on reload.
25
+ - The Dataset combo is a ``ComboBoxSearch`` — type to filter when the
26
+ registry gets large.
27
+ - ``↻`` button next to the Dataset combo re-runs the listing without
28
+ dropping the connection (handy when something else just published a
29
+ new dataset). Auto-load is suppressed on Refresh so the user never
30
+ gets a Load they didn't ask for.
31
+ - ``Delete`` button drops the selected dataset's table and removes
32
+ its row in ``datasets``. Runs through a ``_DeleteDatasetWorker`` on
33
+ a background thread, guarded by a confirmation dialog. Idempotent:
34
+ re-deleting a missing table is a no-op thanks to
35
+ ``DROP TABLE IF EXISTS``.
36
+ - **Auto-load** on workflow reopen: if the persisted
37
+ ``selected_dataset`` is still on the server when the connection
38
+ succeeds and the listing returns, Load fires automatically — the
39
+ data flows out without a single click. The flag is cleared on
40
+ Refresh and on Delete to avoid surprises.
25
41
 
26
42
  **Variable Dependency Graph**
27
43
 
@@ -31,6 +47,17 @@ Unreleased
31
47
  reference the dependency. Plain (non-temporal) references default to
32
48
  weight ``1``. The weights live in ``network.edges[0].edges.data``
33
49
  and are consumable by any downstream Network widget.
50
+ - *New:* the graph is now **directed** (``DirectedEdges``). Previously
51
+ the sparse matrix was passed straight to ``Network`` which auto-wrapped
52
+ it as ``UndirectedEdges`` — a latent bug since A→B is not the same as
53
+ B→A in a dependency graph.
54
+ - *New:* nodes carry an extra ``expression`` meta (literal expression
55
+ text, empty for original variables) so the Network Explorer can show
56
+ the formula as the node label.
57
+ - *New:* ``Warning.no_derived`` fires when every input row is an
58
+ original variable — usually a sign the user wired the data output
59
+ of Time Features Constructor instead of the variable-definitions
60
+ one.
34
61
  - Refactor: flattened the ``from_row_col`` / ``grafo`` decorator pattern
35
62
  into a single, documented ``build_dependency_network`` function.
36
63
  - Performance: O(n³) → O(n²) by precomputing a ``name → index`` map and
@@ -42,6 +69,16 @@ Unreleased
42
69
 
43
70
  **Time Features Constructor**
44
71
 
72
+ - *New:* **chained descriptors**. A descriptor can now reference
73
+ another derived descriptor in its expression (e.g. ``X2 :=
74
+ shift(X1, -1)`` with ``X1`` itself defined a few rows above). The
75
+ widget topologically sorts the descriptors and cascades the
76
+ transforms — each step runs against the table state produced by
77
+ the previous one, so ``X2`` sees ``X1`` as a regular column.
78
+ Cycles (e.g. ``X1 := X2 + 1`` together with ``X2 := X1 + 1``) raise
79
+ a *Circular dependency between descriptors: X1, X2* error. Errors
80
+ during evaluation are reported per-descriptor so the failing row is
81
+ obvious.
45
82
  - *Fixed (critical):* time-window functions used to lose context every
46
83
  5 000 rows because Orange chunks tables during ``transform``. The
47
84
  widget now caches the full source per ``FeatureFunc`` and returns the
@@ -68,6 +105,15 @@ Unreleased
68
105
 
69
106
  **Save to DB**
70
107
 
108
+ - *New:* **write mode** selector with three options — *Create new*
109
+ (default, fail if the target exists), *Overwrite* (drop and
110
+ recreate the table and its ``datasets`` row), *Append* (keep
111
+ existing rows and add the new ones). Re-running a workflow no
112
+ longer breaks. The persisted ``write_mode`` Setting defaults to
113
+ ``"create"`` so old workflows keep their previous behaviour. After
114
+ the upload, the widget runs ``SELECT COUNT(*)`` and rewrites the
115
+ ``datasets`` row with the actual total, so the registry stays
116
+ accurate across appends.
71
117
  - *New:* **MySQL support**. The connection panel now exposes a
72
118
  database-type selector (PostgreSQL / MySQL). Per-dialect column
73
119
  types and identifier quoting (``"name"`` vs ``\`name\```) live in
@@ -48,9 +48,21 @@ Controls
48
48
  host/db (N datasets)", "Loaded <name> (N rows)"), error
49
49
  ("Connection failed: …", "Load failed: …").
50
50
  * - Dataset
51
- - Combo populated from ``SELECT * FROM datasets ORDER BY
52
- datetime DESC``. The most recent upload comes first; the last
53
- choice is restored when reopening a workflow.
51
+ - Searchable combo populated from ``SELECT * FROM datasets ORDER
52
+ BY datetime DESC``. The most recent upload comes first; the
53
+ last choice is restored when reopening a workflow. Type to
54
+ filter the visible items — handy when the registry grows large.
55
+ * - ↻ (Refresh)
56
+ - Small button to the right of the Dataset combo. Re-runs the
57
+ list query without dropping the connection. Useful if another
58
+ Orange canvas (or a parallel tool) just published a new
59
+ dataset while this widget was open.
60
+ * - Delete
61
+ - Drops the currently selected dataset's table from the database
62
+ and removes its row in ``datasets``. Gated by a confirmation
63
+ dialog — there is no Orange-side undo. The operation runs on a
64
+ background thread, so the canvas stays responsive even if the
65
+ table is large.
54
66
  * - Dataset info
55
67
  - Read-only block under the combo: save timestamp, row/column
56
68
  counts and the original class column recorded by **Save to DB**.
@@ -60,7 +72,9 @@ Controls
60
72
  choice, (2) the ``class_name`` stored in the ``datasets``
61
73
  metadata, (3) ``(no class)`` if none of the above apply.
62
74
  * - Load
63
- - Triggers the actual download.
75
+ - Triggers the actual download. Skipped automatically on the
76
+ first connection after a workflow reopen if the persisted
77
+ dataset name is still available (see *Auto-load* below).
64
78
 
65
79
  How it Works
66
80
  ------------
@@ -88,6 +102,28 @@ While any worker runs, the form controls (database type, connection
88
102
  fields, **Connect**, **Load**, dataset and class combos) are
89
103
  temporarily disabled and the status label keeps the user informed.
90
104
 
105
+ Auto-load
106
+ ---------
107
+
108
+ When you reopen a workflow that already had a Load from DB widget with
109
+ a saved ``selected_dataset``, the widget fires Load automatically the
110
+ first time the dataset listing comes back successfully. The data flows
111
+ out of the **Data** output without a single click, mirroring how Orange
112
+ treats sources like **File** and **Datasets**.
113
+
114
+ Subtleties:
115
+
116
+ - Auto-load is a *one-shot* per widget lifetime. Manually clicking
117
+ **Refresh** clears the pending flag, so a Refresh never surprises
118
+ the user by loading something behind their back.
119
+ - If the persisted dataset no longer exists on the server (deleted
120
+ from outside, or the registry was wiped), the flag is cleared and
121
+ the widget just shows the available list — no error.
122
+ - If a different backend or set of credentials is restored, the user
123
+ still has to click **Connect** explicitly, just like in earlier
124
+ versions; auto-load happens *after* the first successful connection
125
+ + listing.
126
+
91
127
  Workflow Persistence
92
128
  --------------------
93
129
 
@@ -59,6 +59,21 @@ TimeFeatures-specific controls:
59
59
  - Destination table name. Validated against PostgreSQL identifier
60
60
  rules (see *Validation* below); MySQL accepts a superset, so
61
61
  the same rule is safe on both.
62
+ * - Mode
63
+ - Combo between the Table name and the Email fields:
64
+
65
+ - **Create new (fail if table exists)** — default, refuses to
66
+ touch an existing table or metadata row. Use this for first
67
+ uploads or when you want a clear error on accidental name
68
+ collisions.
69
+ - **Overwrite (drop and recreate)** — drops the existing data
70
+ table and the matching ``datasets`` row before uploading,
71
+ then recreates them. Re-running the workflow stops breaking.
72
+ - **Append (keep existing rows)** — adds rows to the existing
73
+ table (creating it if it doesn't exist). After the upload,
74
+ the widget runs ``SELECT COUNT(*)`` and rewrites the
75
+ ``datasets`` row with the *actual* total row count, so the
76
+ registry stays accurate across repeated appends.
62
77
  * - Email
63
78
  - Optional notification address. A summary email is sent once the
64
79
  upload finishes, including the table name, row / column counts,
@@ -79,12 +94,32 @@ When **Save** is clicked, the widget:
79
94
 
80
95
  - converts the dataset to a pandas DataFrame with
81
96
  ``Orange.data.pandas_compat.table_to_frame(include_metas=True)``
82
- and reorders the columns (class first, then metas in domain order,
83
- then attributes) to match the existing schema convention;
84
- - creates the ``datasets`` metadata table if it doesn't exist;
85
- - inserts one row into ``datasets`` describing this upload;
86
- - writes the dataset itself in ``PANDAS_SQL_CHUNKSIZE`` chunks
87
- (default ``1 000``) via ``df.to_sql(..., method='multi')``.
97
+ and reorders the columns (class first, then metas in domain
98
+ order, then attributes) to match the existing schema convention;
99
+ - ensures the ``datasets`` metadata table exists;
100
+ - applies the mode-specific preparation:
101
+
102
+ - **create** fail fast if a ``datasets`` row with this name
103
+ already exists.
104
+ - **overwrite** — ``DROP TABLE IF EXISTS`` on the target plus
105
+ ``DELETE`` of the matching ``datasets`` row.
106
+ - **append** — leave everything in place; the upload itself will
107
+ create the table on the first chunk if it doesn't exist.
108
+
109
+ - writes the dataset in ``PANDAS_SQL_CHUNKSIZE`` chunks (default
110
+ ``1 000``) via ``df.to_sql(..., method='multi')`` with the
111
+ ``if_exists`` value tailored to the mode (see
112
+ ``_pandas_if_exists`` for the exact table);
113
+ - runs ``SELECT COUNT(*)`` on the target table and re-inserts the
114
+ ``datasets`` row with that real row count, so the registry stays
115
+ consistent even after multiple appends.
116
+
117
+ Everything happens inside a single SQLAlchemy transaction
118
+ (``engine.begin()``), so a mid-upload error rolls every step back on
119
+ PostgreSQL. MySQL auto-commits DDL (``DROP TABLE``, ``CREATE TABLE``),
120
+ so an Overwrite that crashes during the upload may leave you without
121
+ the original table — there's no way around that without engine-level
122
+ support.
88
123
 
89
124
  While the worker runs, the widget's progress bar and status label are
90
125
  updated through Qt signals; the **Save**, **Connect** and form controls
@@ -144,6 +144,33 @@ returns the appropriate slice for each chunk, so a call like
144
144
  ``shift(x, -20)`` keeps the right value across chunk boundaries even on
145
145
  multi-million-row tables.
146
146
 
147
+ Chained descriptors
148
+ -------------------
149
+
150
+ Descriptors may reference each other. For example:
151
+
152
+ .. code-block:: python
153
+
154
+ X1 := shift(price, -1)
155
+ X2 := X1 + bias
156
+
157
+ When this happens, the widget topologically sorts the descriptors by
158
+ their dependencies and applies them in cascade — each transform step
159
+ runs against the table state produced by the previous step, so ``X2``
160
+ sees ``X1`` as if it were a regular source column.
161
+
162
+ - The order you click **New** in does not matter. Define ``X2`` before
163
+ ``X1`` and the cascade still works.
164
+ - Cycles are detected: writing ``X1 := X2 + 1`` together with
165
+ ``X2 := X1 + 1`` raises a *Circular dependency between descriptors:
166
+ X1, X2* error instead of producing garbage.
167
+ - Per-descriptor error reporting: if one expression fails (e.g.
168
+ ``shift(unknown, -1)``), the error mentions the descriptor name so
169
+ you know which row to fix.
170
+ - Time-window correctness is preserved through the chain: a chained
171
+ ``X2`` that reads ``X1`` over a window keeps producing the right
172
+ values even past Orange's 5 000-row chunk boundary.
173
+
147
174
  Workflow persistence
148
175
  --------------------
149
176
 
@@ -33,9 +33,9 @@ Outputs
33
33
  - Description
34
34
  * - Network
35
35
  - ``orangecontrib.network.Network``
36
- - A directed weighted graph. ``network.edges[0].edges`` is the
37
- sparse adjacency matrix; its non-zero values carry the edge
38
- weights described below.
36
+ - A **directed** weighted graph. ``network.edges[0]`` is a
37
+ ``DirectedEdges`` instance whose sparse adjacency matrix carries
38
+ the per-edge weights described in *Edge Weights* below.
39
39
 
40
40
  How It Works
41
41
  ------------
@@ -118,6 +118,11 @@ downstream styling:
118
118
  * - ``var_type``
119
119
  - Discrete
120
120
  - ``Derived`` (has an expression) or ``Original`` (source feature).
121
+ * - ``expression``
122
+ - String
123
+ - The literal expression text for derived variables; empty for
124
+ original ones. Pick it as **Label** in Network Explorer to see
125
+ each derived node's formula directly on the graph.
121
126
 
122
127
  Controls
123
128
  --------
@@ -126,6 +131,15 @@ Controls
126
131
  table. The widget also auto-regenerates whenever a valid input
127
132
  arrives.
128
133
 
134
+ Warnings
135
+ --------
136
+
137
+ - *Input has no derived variables; the dependency graph is empty.*
138
+ Fires when every row of the configuration table is an original
139
+ variable (no ``Expression`` set). The output network has nodes but
140
+ no edges. Usually means you forgot to attach the second output of
141
+ **Time Features Constructor** instead of the data output.
142
+
129
143
  Input Requirements
130
144
  ------------------
131
145