TimeFeatures 2.2.0__tar.gz → 2.3.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 (85) hide show
  1. {timefeatures-2.2.0/TimeFeatures.egg-info → timefeatures-2.3.0}/PKG-INFO +15 -7
  2. {timefeatures-2.2.0 → timefeatures-2.3.0}/README.md +6 -4
  3. {timefeatures-2.2.0 → timefeatures-2.3.0/TimeFeatures.egg-info}/PKG-INFO +15 -7
  4. {timefeatures-2.2.0 → timefeatures-2.3.0}/TimeFeatures.egg-info/SOURCES.txt +3 -1
  5. {timefeatures-2.2.0 → timefeatures-2.3.0}/TimeFeatures.egg-info/requires.txt +1 -0
  6. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/changes.rst +31 -1
  7. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/index.rst +1 -2
  8. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/installation.rst +27 -4
  9. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/widgets/load-from-db.rst +16 -10
  10. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/widgets/save-to-db.rst +38 -31
  11. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/widgets/time-feature-constructor.rst +16 -4
  12. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/widgets/variable-dependency-graph.rst +20 -11
  13. timefeatures-2.3.0/setup.cfg +4 -0
  14. {timefeatures-2.2.0 → timefeatures-2.3.0}/setup.py +17 -4
  15. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/__version__.py +1 -1
  16. timefeatures-2.2.0/timefeatures/help_html/.buildinfo.bak → timefeatures-2.3.0/timefeatures/help_html/.buildinfo +1 -1
  17. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/changes.rst.txt +31 -1
  18. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/index.rst.txt +1 -2
  19. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/installation.rst.txt +27 -4
  20. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/widgets/load-from-db.rst.txt +16 -10
  21. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/widgets/save-to-db.rst.txt +38 -31
  22. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/widgets/time-feature-constructor.rst.txt +16 -4
  23. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_sources/widgets/variable-dependency-graph.rst.txt +20 -11
  24. timefeatures-2.3.0/timefeatures/help_html/_static/base-stemmer.js +476 -0
  25. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/basic.css +0 -8
  26. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/doctools.js +6 -5
  27. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/documentation_options.js +1 -1
  28. timefeatures-2.3.0/timefeatures/help_html/_static/english-stemmer.js +1066 -0
  29. timefeatures-2.3.0/timefeatures/help_html/_static/language_data.js +13 -0
  30. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/searchtools.js +121 -60
  31. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/sphinx_highlight.js +36 -31
  32. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/changes.html +37 -7
  33. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/genindex.html +4 -4
  34. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/index.html +6 -7
  35. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/installation.html +33 -11
  36. timefeatures-2.3.0/timefeatures/help_html/objects.inv +11 -0
  37. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/search.html +4 -4
  38. timefeatures-2.3.0/timefeatures/help_html/searchindex.js +1 -0
  39. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/widgets/load-from-db.html +20 -14
  40. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/widgets/save-to-db.html +41 -36
  41. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/widgets/time-feature-constructor.html +19 -8
  42. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/widgets/variable-dependency-graph.html +24 -15
  43. timefeatures-2.3.0/timefeatures/widgets/_threads.py +88 -0
  44. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/owloadfromdb.py +43 -46
  45. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/owsavetodb.py +131 -47
  46. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/owtimefeaturesconstructor.py +65 -110
  47. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/owvardependencygraph.py +108 -49
  48. timefeatures-2.2.0/setup.cfg +0 -7
  49. timefeatures-2.2.0/timefeatures/help_html/_static/language_data.js +0 -192
  50. timefeatures-2.2.0/timefeatures/help_html/objects.inv +0 -0
  51. timefeatures-2.2.0/timefeatures/help_html/searchindex.js +0 -1
  52. {timefeatures-2.2.0 → timefeatures-2.3.0}/LICENSE +0 -0
  53. {timefeatures-2.2.0 → timefeatures-2.3.0}/MANIFEST.in +0 -0
  54. {timefeatures-2.2.0 → timefeatures-2.3.0}/TimeFeatures.egg-info/dependency_links.txt +0 -0
  55. {timefeatures-2.2.0 → timefeatures-2.3.0}/TimeFeatures.egg-info/entry_points.txt +0 -0
  56. {timefeatures-2.2.0 → timefeatures-2.3.0}/TimeFeatures.egg-info/top_level.txt +0 -0
  57. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/Makefile +0 -0
  58. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/conf.py +0 -0
  59. {timefeatures-2.2.0 → timefeatures-2.3.0}/docs/requirements.txt +0 -0
  60. {timefeatures-2.2.0 → timefeatures-2.3.0}/imgs/installation.png +0 -0
  61. {timefeatures-2.2.0 → timefeatures-2.3.0}/imgs/widgets/owloadfromdb.png +0 -0
  62. {timefeatures-2.2.0 → timefeatures-2.3.0}/imgs/widgets/owsavetodb.png +0 -0
  63. {timefeatures-2.2.0 → timefeatures-2.3.0}/imgs/widgets/owtimefeaturesconstructor.png +0 -0
  64. {timefeatures-2.2.0 → timefeatures-2.3.0}/imgs/widgets/owvariabledependencygraph.png +0 -0
  65. {timefeatures-2.2.0 → timefeatures-2.3.0}/imgs/workflow.png +0 -0
  66. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/__init__.py +0 -0
  67. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help.py +0 -0
  68. /timefeatures-2.2.0/timefeatures/help_html/.buildinfo → /timefeatures-2.3.0/timefeatures/help_html/.buildinfo.bak +0 -0
  69. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_images/owloadfromdb.png +0 -0
  70. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_images/owsavetodb.png +0 -0
  71. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_images/owtimefeaturesconstructor.png +0 -0
  72. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_images/owvariabledependencygraph.png +0 -0
  73. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/alabaster.css +0 -0
  74. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/custom.css +0 -0
  75. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/file.png +0 -0
  76. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/github-banner.svg +0 -0
  77. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/minus.png +0 -0
  78. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/plus.png +0 -0
  79. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/help_html/_static/pygments.css +0 -0
  80. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/__init__.py +0 -0
  81. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/icons/graphgenerator.svg +0 -0
  82. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/icons/loaddatadb.svg +0 -0
  83. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/icons/savedatadb.svg +0 -0
  84. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/icons/timefeature-xs.svg +0 -0
  85. {timefeatures-2.2.0 → timefeatures-2.3.0}/timefeatures/widgets/icons/timefeature.svg +0 -0
@@ -1,23 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TimeFeatures
3
- Version: 2.2.0
3
+ Version: 2.3.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
7
7
  Author-email: alejandrorivasgarcia@gmail.com
8
- License: GPL3+
8
+ License: GPL-3.0-or-later
9
9
  Keywords: orange3 add-on,timefeatures,graph,time series,data mining,graph visualization,orange,addon,synthetic data
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Environment :: Plugins
12
12
  Classifier: Programming Language :: Python
13
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
14
17
  Classifier: Operating System :: OS Independent
15
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
19
  Classifier: Intended Audience :: Education
17
20
  Classifier: Intended Audience :: Science/Research
18
21
  Classifier: Intended Audience :: Developers
22
+ Requires-Python: >=3.10
19
23
  Description-Content-Type: text/markdown
20
24
  License-File: LICENSE
25
+ Requires-Dist: Orange3>=3.36.2
21
26
  Requires-Dist: numpy>=1.22.4
22
27
  Requires-Dist: AnyQt>=0.2.0
23
28
  Requires-Dist: PyQt5>=5.15.6
@@ -40,13 +45,15 @@ Dynamic: license
40
45
  Dynamic: license-file
41
46
  Dynamic: provides-extra
42
47
  Dynamic: requires-dist
48
+ Dynamic: requires-python
43
49
  Dynamic: summary
44
50
 
45
51
  # Orange3 TimeFeatures
46
52
 
47
53
  [![PyPI version](https://img.shields.io/pypi/v/TimeFeatures)](https://pypi.org/project/TimeFeatures/)
48
54
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
49
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
55
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
56
+ [![CI](https://github.com/alervgr/Orange-TimeFeatures/actions/workflows/ci.yml/badge.svg)](https://github.com/alervgr/Orange-TimeFeatures/actions/workflows/ci.yml)
50
57
  [![Orange3](https://img.shields.io/badge/Orange3-add--on-orange)](https://orangedatamining.com/)
51
58
 
52
59
  TimeFeatures is an add-on for [Orange] 3 data mining software for generating synthetic data using datasets with time series, generating graphs of relationships between the generated variables, and includes widgets to save and load data and configuration tables from a database.
@@ -57,7 +64,7 @@ TimeFeatures is an add-on for [Orange] 3 data mining software for generating syn
57
64
 
58
65
  - 🕐 **7 time-window functions** — `shift`, `sum`, `mean`, `min`, `max`, `count`, `sd` with full chunk-boundary correctness
59
66
  - 🔗 **Chained descriptors** — derived variables can reference each other; topological sort resolves the evaluation order automatically
60
- - 🛡️ **Secure evaluation** — expressions run in a restricted `eval` sandbox (`__builtins__` replaced, curated whitelist only)
67
+ - 🧮 **Python-style expressions** — evaluated with a restricted `eval` (`__builtins__` replaced, curated whitelist only). This is **not** a security sandbox: expressions can still run arbitrary code, so only open workflows from sources you trust
61
68
  - 🗄️ **PostgreSQL & MySQL** — persist and reload datasets via SQLAlchemy with dialect-agnostic SQL generation
62
69
  - 📊 **Directed weighted dependency graphs** — edge weights reflect temporal window size; visualise in Network Explorer
63
70
  - ⚡ **Bulk upload performance** — pandas `DataFrame.to_sql` with chunked multi-row INSERTs
@@ -69,7 +76,7 @@ TimeFeatures is an add-on for [Orange] 3 data mining software for generating syn
69
76
  |---|---|
70
77
  | **Time Features Constructor** | Defines new variables from existing ones using Python-style expressions and time-window functions. Supports chained descriptors with automatic topological sorting. |
71
78
  | **Variable Dependency Graph** | Builds a directed, weighted dependency graph from the variable definitions. Edge weights summarise how far back or forward in time each variable looks. |
72
- | **Save to DB** | Persists the resulting dataset to a SQL database (PostgreSQL or MySQL), with full SQL-injection defences, three write modes (create / overwrite / append) and an optional completion email. |
79
+ | **Save to DB** | Persists the resulting dataset to a SQL database (PostgreSQL or MySQL), with full SQL-injection defences and three write modes (create / overwrite / append). |
73
80
  | **Load from DB** | Lists datasets previously stored by Save to DB and pulls the chosen one back into Orange, optionally marking the class column directly so no Select Columns widget is needed. |
74
81
 
75
82
  ## Installation
@@ -96,8 +103,9 @@ If using Anaconda Python distribution, simply run
96
103
 
97
104
  pip install TimeFeatures
98
105
 
99
- **Required Dependencies**:
106
+ **Required Dependencies** (Python 3.10 or newer):
100
107
 
108
+ * Orange3>=3.36.2
101
109
  * numpy>=1.22.4
102
110
  * AnyQt>=0.2.0
103
111
  * PyQt5>=5.15.6
@@ -2,7 +2,8 @@
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/TimeFeatures)](https://pypi.org/project/TimeFeatures/)
4
4
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
5
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
6
+ [![CI](https://github.com/alervgr/Orange-TimeFeatures/actions/workflows/ci.yml/badge.svg)](https://github.com/alervgr/Orange-TimeFeatures/actions/workflows/ci.yml)
6
7
  [![Orange3](https://img.shields.io/badge/Orange3-add--on-orange)](https://orangedatamining.com/)
7
8
 
8
9
  TimeFeatures is an add-on for [Orange] 3 data mining software for generating synthetic data using datasets with time series, generating graphs of relationships between the generated variables, and includes widgets to save and load data and configuration tables from a database.
@@ -13,7 +14,7 @@ TimeFeatures is an add-on for [Orange] 3 data mining software for generating syn
13
14
 
14
15
  - 🕐 **7 time-window functions** — `shift`, `sum`, `mean`, `min`, `max`, `count`, `sd` with full chunk-boundary correctness
15
16
  - 🔗 **Chained descriptors** — derived variables can reference each other; topological sort resolves the evaluation order automatically
16
- - 🛡️ **Secure evaluation** — expressions run in a restricted `eval` sandbox (`__builtins__` replaced, curated whitelist only)
17
+ - 🧮 **Python-style expressions** — evaluated with a restricted `eval` (`__builtins__` replaced, curated whitelist only). This is **not** a security sandbox: expressions can still run arbitrary code, so only open workflows from sources you trust
17
18
  - 🗄️ **PostgreSQL & MySQL** — persist and reload datasets via SQLAlchemy with dialect-agnostic SQL generation
18
19
  - 📊 **Directed weighted dependency graphs** — edge weights reflect temporal window size; visualise in Network Explorer
19
20
  - ⚡ **Bulk upload performance** — pandas `DataFrame.to_sql` with chunked multi-row INSERTs
@@ -25,7 +26,7 @@ TimeFeatures is an add-on for [Orange] 3 data mining software for generating syn
25
26
  |---|---|
26
27
  | **Time Features Constructor** | Defines new variables from existing ones using Python-style expressions and time-window functions. Supports chained descriptors with automatic topological sorting. |
27
28
  | **Variable Dependency Graph** | Builds a directed, weighted dependency graph from the variable definitions. Edge weights summarise how far back or forward in time each variable looks. |
28
- | **Save to DB** | Persists the resulting dataset to a SQL database (PostgreSQL or MySQL), with full SQL-injection defences, three write modes (create / overwrite / append) and an optional completion email. |
29
+ | **Save to DB** | Persists the resulting dataset to a SQL database (PostgreSQL or MySQL), with full SQL-injection defences and three write modes (create / overwrite / append). |
29
30
  | **Load from DB** | Lists datasets previously stored by Save to DB and pulls the chosen one back into Orange, optionally marking the class column directly so no Select Columns widget is needed. |
30
31
 
31
32
  ## Installation
@@ -52,8 +53,9 @@ If using Anaconda Python distribution, simply run
52
53
 
53
54
  pip install TimeFeatures
54
55
 
55
- **Required Dependencies**:
56
+ **Required Dependencies** (Python 3.10 or newer):
56
57
 
58
+ * Orange3>=3.36.2
57
59
  * numpy>=1.22.4
58
60
  * AnyQt>=0.2.0
59
61
  * PyQt5>=5.15.6
@@ -1,23 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TimeFeatures
3
- Version: 2.2.0
3
+ Version: 2.3.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
7
7
  Author-email: alejandrorivasgarcia@gmail.com
8
- License: GPL3+
8
+ License: GPL-3.0-or-later
9
9
  Keywords: orange3 add-on,timefeatures,graph,time series,data mining,graph visualization,orange,addon,synthetic data
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Environment :: Plugins
12
12
  Classifier: Programming Language :: Python
13
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
14
17
  Classifier: Operating System :: OS Independent
15
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
19
  Classifier: Intended Audience :: Education
17
20
  Classifier: Intended Audience :: Science/Research
18
21
  Classifier: Intended Audience :: Developers
22
+ Requires-Python: >=3.10
19
23
  Description-Content-Type: text/markdown
20
24
  License-File: LICENSE
25
+ Requires-Dist: Orange3>=3.36.2
21
26
  Requires-Dist: numpy>=1.22.4
22
27
  Requires-Dist: AnyQt>=0.2.0
23
28
  Requires-Dist: PyQt5>=5.15.6
@@ -40,13 +45,15 @@ Dynamic: license
40
45
  Dynamic: license-file
41
46
  Dynamic: provides-extra
42
47
  Dynamic: requires-dist
48
+ Dynamic: requires-python
43
49
  Dynamic: summary
44
50
 
45
51
  # Orange3 TimeFeatures
46
52
 
47
53
  [![PyPI version](https://img.shields.io/pypi/v/TimeFeatures)](https://pypi.org/project/TimeFeatures/)
48
54
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
49
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
55
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
56
+ [![CI](https://github.com/alervgr/Orange-TimeFeatures/actions/workflows/ci.yml/badge.svg)](https://github.com/alervgr/Orange-TimeFeatures/actions/workflows/ci.yml)
50
57
  [![Orange3](https://img.shields.io/badge/Orange3-add--on-orange)](https://orangedatamining.com/)
51
58
 
52
59
  TimeFeatures is an add-on for [Orange] 3 data mining software for generating synthetic data using datasets with time series, generating graphs of relationships between the generated variables, and includes widgets to save and load data and configuration tables from a database.
@@ -57,7 +64,7 @@ TimeFeatures is an add-on for [Orange] 3 data mining software for generating syn
57
64
 
58
65
  - 🕐 **7 time-window functions** — `shift`, `sum`, `mean`, `min`, `max`, `count`, `sd` with full chunk-boundary correctness
59
66
  - 🔗 **Chained descriptors** — derived variables can reference each other; topological sort resolves the evaluation order automatically
60
- - 🛡️ **Secure evaluation** — expressions run in a restricted `eval` sandbox (`__builtins__` replaced, curated whitelist only)
67
+ - 🧮 **Python-style expressions** — evaluated with a restricted `eval` (`__builtins__` replaced, curated whitelist only). This is **not** a security sandbox: expressions can still run arbitrary code, so only open workflows from sources you trust
61
68
  - 🗄️ **PostgreSQL & MySQL** — persist and reload datasets via SQLAlchemy with dialect-agnostic SQL generation
62
69
  - 📊 **Directed weighted dependency graphs** — edge weights reflect temporal window size; visualise in Network Explorer
63
70
  - ⚡ **Bulk upload performance** — pandas `DataFrame.to_sql` with chunked multi-row INSERTs
@@ -69,7 +76,7 @@ TimeFeatures is an add-on for [Orange] 3 data mining software for generating syn
69
76
  |---|---|
70
77
  | **Time Features Constructor** | Defines new variables from existing ones using Python-style expressions and time-window functions. Supports chained descriptors with automatic topological sorting. |
71
78
  | **Variable Dependency Graph** | Builds a directed, weighted dependency graph from the variable definitions. Edge weights summarise how far back or forward in time each variable looks. |
72
- | **Save to DB** | Persists the resulting dataset to a SQL database (PostgreSQL or MySQL), with full SQL-injection defences, three write modes (create / overwrite / append) and an optional completion email. |
79
+ | **Save to DB** | Persists the resulting dataset to a SQL database (PostgreSQL or MySQL), with full SQL-injection defences and three write modes (create / overwrite / append). |
73
80
  | **Load from DB** | Lists datasets previously stored by Save to DB and pulls the chosen one back into Orange, optionally marking the class column directly so no Select Columns widget is needed. |
74
81
 
75
82
  ## Installation
@@ -96,8 +103,9 @@ If using Anaconda Python distribution, simply run
96
103
 
97
104
  pip install TimeFeatures
98
105
 
99
- **Required Dependencies**:
106
+ **Required Dependencies** (Python 3.10 or newer):
100
107
 
108
+ * Orange3>=3.36.2
101
109
  * numpy>=1.22.4
102
110
  * AnyQt>=0.2.0
103
111
  * PyQt5>=5.15.6
@@ -1,7 +1,6 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
- setup.cfg
5
4
  setup.py
6
5
  TimeFeatures.egg-info/PKG-INFO
7
6
  TimeFeatures.egg-info/SOURCES.txt
@@ -49,10 +48,12 @@ timefeatures/help_html/_sources/widgets/save-to-db.rst.txt
49
48
  timefeatures/help_html/_sources/widgets/time-feature-constructor.rst.txt
50
49
  timefeatures/help_html/_sources/widgets/variable-dependency-graph.rst.txt
51
50
  timefeatures/help_html/_static/alabaster.css
51
+ timefeatures/help_html/_static/base-stemmer.js
52
52
  timefeatures/help_html/_static/basic.css
53
53
  timefeatures/help_html/_static/custom.css
54
54
  timefeatures/help_html/_static/doctools.js
55
55
  timefeatures/help_html/_static/documentation_options.js
56
+ timefeatures/help_html/_static/english-stemmer.js
56
57
  timefeatures/help_html/_static/file.png
57
58
  timefeatures/help_html/_static/github-banner.svg
58
59
  timefeatures/help_html/_static/language_data.js
@@ -66,6 +67,7 @@ timefeatures/help_html/widgets/save-to-db.html
66
67
  timefeatures/help_html/widgets/time-feature-constructor.html
67
68
  timefeatures/help_html/widgets/variable-dependency-graph.html
68
69
  timefeatures/widgets/__init__.py
70
+ timefeatures/widgets/_threads.py
69
71
  timefeatures/widgets/owloadfromdb.py
70
72
  timefeatures/widgets/owsavetodb.py
71
73
  timefeatures/widgets/owtimefeaturesconstructor.py
@@ -1,3 +1,4 @@
1
+ Orange3>=3.36.2
1
2
  numpy>=1.22.4
2
3
  AnyQt>=0.2.0
3
4
  PyQt5>=5.15.6
@@ -1,6 +1,36 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ 2.3.0
5
+ -----
6
+
7
+ **Fixes**
8
+
9
+ - *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(``.
10
+ - *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``.
11
+ - *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*.
12
+ - *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_``).
13
+ - *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.
14
+ - *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.
15
+ - *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*).
16
+ - *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.
17
+ - *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.
18
+ - *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.
19
+
20
+ **Packaging & CI**
21
+
22
+ - ``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+.
23
+ - 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.
24
+ - Removed ``setup.cfg``: its only key (``description-file``) is deprecated and will stop being accepted by setuptools; ``setup.py`` already sets the long description.
25
+ - 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.
26
+ - 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``.
27
+
28
+ **Documentation**
29
+
30
+ - 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.
31
+ - Removed the completion-email references from the README, the index and the Save to DB page: notifications are currently disabled.
32
+ - Rebuilt the bundled in-app help (``timefeatures/help_html``) so the Orange Help window shows the updated pages.
33
+
4
34
  2.2.0 — 2026-06-09
5
35
  -------------------
6
36
 
@@ -11,7 +41,7 @@ Changelog
11
41
  - *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.
12
42
  - *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.
13
43
 
14
- 2.1.1 — 2026-06-09
44
+ 2.1.1
15
45
  -------------------
16
46
 
17
47
  **Load from DB (new widget)**
@@ -22,8 +22,7 @@ built on top of an existing dataset.
22
22
  how far back or forward in time each variable looks.
23
23
  * - :doc:`widgets/save-to-db`
24
24
  - Persists the resulting dataset to a SQL database (PostgreSQL
25
- or MySQL), with full SQL-injection defences and an optional
26
- completion email.
25
+ or MySQL), with full SQL-injection defences.
27
26
  * - :doc:`widgets/load-from-db`
28
27
  - Lists the datasets previously stored by Save to DB and pulls
29
28
  the chosen one back into Orange, optionally marking the class
@@ -50,8 +50,8 @@ environment as long as Orange3 is installed there.
50
50
  Dependencies
51
51
  ------------
52
52
 
53
- |addon| declares the following runtime dependencies (see
54
- ``setup.py``):
53
+ |addon| needs Python 3.10 or newer and declares the following runtime
54
+ dependencies (see ``setup.py``):
55
55
 
56
56
  .. list-table::
57
57
  :header-rows: 1
@@ -59,6 +59,10 @@ Dependencies
59
59
  * - Package
60
60
  - Minimum version
61
61
  - Used by
62
+ * - ``Orange3``
63
+ - 3.36.2
64
+ - Host application and data model. Older releases do not work with
65
+ NumPy 2.
62
66
  * - ``numpy``
63
67
  - 1.22.4
64
68
  - All widgets.
@@ -68,6 +72,9 @@ Dependencies
68
72
  * - ``PyQt5``
69
73
  - 5.15.6
70
74
  - Widget UI.
75
+ * - ``PyQtWebEngine``
76
+ - 5.15.6
77
+ - Orange's in-app help window.
71
78
  * - ``AnyQt``
72
79
  - 0.2.0
73
80
  - Qt abstraction used by Orange.
@@ -84,8 +91,15 @@ Dependencies
84
91
  - 1.8.0
85
92
  - ``Network`` type produced by **Variable Dependency Graph**.
86
93
 
87
- Orange3 itself is intentionally left out of the requirements list the
88
- host application provides it.
94
+ Orange3 is listed explicitly so a clean ``pip install`` gets a version
95
+ the add-on is tested with. Inside an existing Orange installation that
96
+ already meets the minimum, pip leaves it untouched.
97
+
98
+ The CI workflow (``.github/workflows/ci.yml``) runs the test suite on
99
+ Python 3.10 with Orange3 3.36.2 and on Python 3.11 and 3.12 with the
100
+ latest releases, builds the package and installs the wheel into an
101
+ empty environment, and builds this documentation with warnings as
102
+ errors.
89
103
 
90
104
  Running tests
91
105
  -------------
@@ -101,3 +115,12 @@ The test suite uses ``unittest`` and lives under
101
115
  The widget-level tests inherit from ``Orange.widgets.tests.base.WidgetTest``
102
116
  and therefore require Qt; a virtual display (``Xvfb``) is enough on a
103
117
  headless CI worker.
118
+
119
+ The Save to DB upload tests always run on SQLite. To also run them
120
+ against real servers, point these variables to **disposable** databases
121
+ (the tests create and drop tables there):
122
+
123
+ .. code-block:: bash
124
+
125
+ export TIMEFEATURES_TEST_POSTGRES_URL=postgresql://user:pass@localhost:5432/timefeatures_test
126
+ export TIMEFEATURES_TEST_MYSQL_URL=mysql://user:pass@127.0.0.1:3306/timefeatures_test
@@ -90,22 +90,28 @@ then spawns a background ``QThread`` that runs a
90
90
  ``_ListDatasetsWorker``. The worker queries the ``datasets`` metadata
91
91
  table via SQLAlchemy and emits the result back to the GUI thread.
92
92
 
93
- Selecting a dataset triggers a tiny ``SELECT * FROM <name> LIMIT 0``
94
- to fetch the column list this is cheap enough to run synchronously
95
- because the server only returns the column header.
96
-
97
- When **Load** is clicked, a ``_LoadTableWorker`` runs
98
- ``pandas.read_sql("SELECT * FROM <name>", engine)`` on a background
99
- thread, returning a DataFrame that the GUI thread converts into an
100
- ``Orange.data.Table`` with
101
- ``Orange.data.pandas_compat.table_from_frame``. If the user picked a
102
- class column, the helper ``_build_domain_with_class`` rebuilds the
93
+ Selecting a dataset runs ``SELECT * FROM <name> LIMIT 50`` to fill the
94
+ preview and the column list. This small query still runs on the GUI
95
+ thread, so on a slow connection the widget pauses briefly.
96
+
97
+ When **Load** is clicked, a ``_LoadTableWorker`` reads the table on a
98
+ background thread in 1 000-row chunks through a server-side cursor
99
+ (``stream_results``), so the progress bar follows the rows as they
100
+ arrive and **Cancel** stops the load within one chunk. The resulting
101
+ DataFrame is converted in the GUI thread into an ``Orange.data.Table``
102
+ with ``Orange.data.pandas_compat.table_from_frame``. If the user picked
103
+ a class column, the helper ``_build_domain_with_class`` rebuilds the
103
104
  domain so that column becomes ``domain.class_var`` and the rest of the
104
105
  columns stay in ``domain.attributes``.
105
106
 
106
107
  While any worker runs, the form controls (database type, connection
107
108
  fields, **Connect**, **Load**, dataset and class combos) are
108
109
  temporarily disabled and the status label keeps the user informed.
110
+ Each operation keeps its own thread, so an operation started from the
111
+ end of another (the auto-load right after the listing, or the listing
112
+ right after a delete) is never cut short or left uncancellable.
113
+ Removing the widget or closing the workflow during a load cancels it
114
+ instead of freezing the canvas until the query ends.
109
115
 
110
116
  Auto-load
111
117
  ---------
@@ -65,32 +65,34 @@ TimeFeatures-specific controls:
65
65
  rules (see *Validation* below); MySQL accepts a superset, so
66
66
  the same rule is safe on both.
67
67
  * - Mode
68
- - Combo between the Table name and the Email fields:
68
+ - Combo below the Table name field:
69
69
 
70
70
  - **Create new (fail if table exists)** — default, refuses to
71
71
  touch an existing table or metadata row. Use this for first
72
72
  uploads or when you want a clear error on accidental name
73
73
  collisions.
74
- - **Overwrite (drop and recreate)** — drops the existing data
75
- table and the matching ``datasets`` row before uploading,
76
- then recreates them. Re-running the workflow stops breaking.
74
+ - **Overwrite (replace existing table)** — uploads the new data
75
+ and only then replaces the existing table and its
76
+ ``datasets`` row. If the upload fails or is cancelled, the
77
+ previous table is kept. Re-running the workflow stops
78
+ breaking.
77
79
  - **Append (keep existing rows)** — adds rows to the existing
78
80
  table (creating it if it doesn't exist). After the upload,
79
81
  the widget runs ``SELECT COUNT(*)`` and rewrites the
80
82
  ``datasets`` row with the *actual* total row count, so the
81
83
  registry stays accurate across repeated appends.
82
- * - Email
83
- - Optional notification address. A summary email is sent once the
84
- upload finishes, including the table name, row / column counts,
85
- and elapsed time.
84
+
85
+ .. note::
86
+
87
+ Completion-email notifications are currently disabled; the Email
88
+ field is hidden until they are re-enabled.
86
89
 
87
90
  How it Works
88
91
  ------------
89
92
 
90
93
  When **Save** is clicked, the widget:
91
94
 
92
- 1. Validates the table name and the form (host, database, optional
93
- email).
95
+ 1. Validates the table name and the form (host, database).
94
96
  2. Builds a SQLAlchemy URL from the form fields and the active dialect
95
97
  driver (``postgresql+psycopg2://…`` or ``mysql+pymysql://…``).
96
98
  3. Spawns a background ``QThread`` running an ``_UploadWorker``, so the
@@ -102,33 +104,40 @@ When **Save** is clicked, the widget:
102
104
  and reorders the columns (class first, then metas in domain
103
105
  order, then attributes) to match the existing schema convention;
104
106
  - ensures the ``datasets`` metadata table exists;
105
- - applies the mode-specific preparation:
106
-
107
- - **create** fail fast if a ``datasets`` row with this name
108
- already exists.
109
- - **overwrite** — ``DROP TABLE IF EXISTS`` on the target plus
110
- ``DELETE`` of the matching ``datasets`` row.
111
- - **append** — leave everything in place; the upload itself will
112
- create the table on the first chunk if it doesn't exist.
113
-
107
+ - applies the mode-specific checks: **create** fails fast if a
108
+ ``datasets`` row or a table with this name already exists;
109
+ **overwrite** and **append** leave everything in place;
114
110
  - writes the dataset in ``PANDAS_SQL_CHUNKSIZE`` chunks (default
115
- ``1 000``) via ``df.to_sql(..., method='multi')`` with the
116
- ``if_exists`` value tailored to the mode (see
117
- ``_pandas_if_exists`` for the exact table);
111
+ ``1 000``) via ``df.to_sql(..., method='multi')``. **Append** into
112
+ an existing table writes straight into it. Every other case writes
113
+ into a uniquely named staging table (``_tf_staging_…``);
114
+ - once every chunk is in, swaps the staging table in place of the
115
+ target: ``DROP TABLE`` + ``ALTER TABLE … RENAME`` on PostgreSQL, a
116
+ single multi-table ``RENAME TABLE`` on MySQL;
118
117
  - runs ``SELECT COUNT(*)`` on the target table and re-inserts the
119
118
  ``datasets`` row with that real row count, so the registry stays
120
119
  consistent even after multiple appends.
121
120
 
122
121
  Everything happens inside a single SQLAlchemy transaction
123
- (``engine.begin()``), so a mid-upload error rolls every step back on
124
- PostgreSQL. MySQL auto-commits DDL (``DROP TABLE``, ``CREATE TABLE``),
125
- so an Overwrite that crashes during the upload may leave you without
126
- the original table there's no way around that without engine-level
127
- support.
122
+ (``engine.begin()``). On PostgreSQL, DDL is transactional, so a
123
+ mid-upload error or a **Cancel** rolls every step back. MySQL
124
+ auto-commits DDL (``CREATE TABLE``, ``RENAME TABLE``), which is why the
125
+ upload goes through a staging table: until the final swap the target
126
+ table is untouched, and on failure the widget drops the staging table
127
+ again. Appended rows are ordinary ``INSERT`` statements and are rolled
128
+ back on both engines.
129
+
130
+ .. note::
131
+
132
+ If Orange is killed in the middle of an upload to MySQL, the staging
133
+ table cannot be cleaned up and stays behind as ``_tf_staging_…``. It
134
+ never appears in **Load from DB** and can be dropped by hand.
128
135
 
129
136
  While the worker runs, the widget's progress bar and status label are
130
137
  updated through Qt signals; the **Save**, **Connect** and form controls
131
- are temporarily disabled.
138
+ are temporarily disabled. Removing the widget or closing the workflow
139
+ during an upload cancels it (rolled back as described above) instead
140
+ of freezing the canvas until the upload ends.
132
141
 
133
142
  Type Mapping
134
143
  ------------
@@ -175,7 +184,6 @@ Before touching the database the widget enforces:
175
184
  starts with a letter or underscore, only letters / digits /
176
185
  underscores, max 63 characters). MySQL accepts a superset.
177
186
  - **Connection fields present** — host and database.
178
- - **Email well-formed** — only when the optional field is filled.
179
187
 
180
188
  Security
181
189
  --------
@@ -203,8 +211,7 @@ Usage Example
203
211
  4. Click **Connect**. The status label should turn green with the
204
212
  connection details.
205
213
  5. Enter a valid table name, e.g. ``my_time_series_data``.
206
- 6. *(Optional)* Enter an email for the completion notification.
207
- 7. Click **Save**. Watch the progress bar advance through the chunks
214
+ 6. Click **Save**. Watch the progress bar advance through the chunks
208
215
  and the status label switch to *Upload completed in Xs* when done.
209
216
 
210
217
  Requirements
@@ -74,9 +74,21 @@ underscore.
74
74
  mean(temperature, -2, 2)
75
75
  abs(velocity) + sqrt(altitude)
76
76
 
77
- The evaluation environment is locked down: ``__builtins__`` is replaced
78
- with an empty dict so dangerous calls like ``__import__`` or ``open``
79
- fail with ``NameError``. Only the names listed below are exposed.
77
+ Spaces around the arguments of the time-window functions are optional:
78
+ ``shift(age,-20)`` and ``shift(age, -20)`` are equivalent. An expression
79
+ that references no column (e.g. ``42``) produces that value on every
80
+ row.
81
+
82
+ ``__builtins__`` is replaced with an empty dict and only the names
83
+ listed below are exposed, so a direct call like ``__import__`` or
84
+ ``open`` fails with ``NameError``.
85
+
86
+ .. warning::
87
+
88
+ This restricted ``eval`` is **not** a security sandbox. Python
89
+ introspection can still escape it and run arbitrary code, and
90
+ expressions stored in a workflow run as soon as the workflow is
91
+ opened. Only open workflows from sources you trust.
80
92
 
81
93
  Available names
82
94
  ~~~~~~~~~~~~~~~
@@ -87,7 +99,7 @@ Available names
87
99
 
88
100
  * - Group
89
101
  - Names
90
- * - Safe builtins
102
+ * - Builtins
91
103
  - ``abs``, ``all``, ``any``, ``bin``, ``bool``, ``bytearray``,
92
104
  ``bytes``, ``chr``, ``complex``, ``dict``, ``divmod``,
93
105
  ``enumerate``, ``filter``, ``float``, ``format``, ``frozenset``,
@@ -52,10 +52,14 @@ The widget processes the input table row by row:
52
52
  - A row with a valid expression is treated as a **derived variable**
53
53
  (``var_type = Derived``).
54
54
 
55
- For every derived variable, the widget scans its expression for
56
- references to known variable names. Every reference produces one
57
- directed edge **source → dependency** (i.e. an edge ``X1 → X2`` means
58
- "X1's expression references X2").
55
+ For every derived variable, the widget parses its expression and
56
+ collects the known variable names it uses as values. Every reference
57
+ produces one directed edge **source → dependency** (i.e. an edge
58
+ ``X1 → X2`` means "X1's expression references X2"). Names inside string
59
+ literals, names bound inside a comprehension or lambda, and the names
60
+ of called functions (``sqrt``, ``shift``…) are not references. If an
61
+ expression cannot be parsed, its variable gets no edges and the widget
62
+ shows a warning naming it.
59
63
 
60
64
  Edge Weights
61
65
  ------------
@@ -71,7 +75,9 @@ For an edge ``Xᵢ → Xⱼ``:
71
75
  - If ``Xⱼ`` appears inside one or more **temporal calls** in ``Xᵢ``'s
72
76
  expression (``shift``, ``sum``, ``mean``, ``count``, ``min``, ``max``,
73
77
  ``sd``), the weight is the **maximum absolute integer argument**
74
- across all such calls.
78
+ across all such calls. A temporal call counts only in the form the
79
+ Time Features Constructor evaluates: ``shift(var, n)`` or
80
+ ``f(var, n, m)`` with integer literals.
75
81
  - If ``Xⱼ`` only appears outside temporal calls (e.g. ``Xⱼ + 1``), the
76
82
  weight defaults to ``1``.
77
83
 
@@ -119,7 +125,8 @@ downstream styling:
119
125
  - Values
120
126
  * - ``var_name``
121
127
  - String
122
- - Sanitised variable name (spaces and hyphens become ``_``).
128
+ - Sanitised variable name (every non-alphanumeric character
129
+ becomes ``_``; a leading digit gets a ``_`` prefix).
123
130
  * - ``var_type``
124
131
  - Discrete
125
132
  - ``Derived`` (has an expression) or ``Original`` (source feature).
@@ -172,8 +179,10 @@ Implementation notes
172
179
 
173
180
  - Variable name lookup is **O(1)** via a precomputed ``name → index``
174
181
  map, so the full graph build is linear in the number of references.
175
- - The detection regex uses word boundaries (``\b``), so ``X1`` will not
176
- match inside ``X10``.
177
- - Sanitisation maps spaces and hyphens to underscores so the names line
178
- up with how the **Time Features Constructor** rewrites them inside
179
- expressions.
182
+ - References and temporal calls are read from the expression's syntax
183
+ tree (``ast``), reusing the constructor's ``freevars``, so ``X1``
184
+ never matches inside ``X10`` or inside a string.
185
+ - Sanitisation reuses the **Time Features Constructor**'s
186
+ ``sanitized_name`` (plus NFKC normalisation, as Python applies to
187
+ identifiers) so the names line up with how they are written inside
188
+ expressions (``temp (C)`` → ``temp__C_``, ``1abc`` → ``_1abc``).
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+