dl2-tasks 0.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.
- dl2_tasks-0.1.0/LICENSE +21 -0
- dl2_tasks-0.1.0/PKG-INFO +120 -0
- dl2_tasks-0.1.0/README.md +69 -0
- dl2_tasks-0.1.0/pyproject.toml +46 -0
- dl2_tasks-0.1.0/setup.cfg +4 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/cli.py +107 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/client/__init__.py +1 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/client/decorator.py +116 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/core/__init__.py +1 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/core/config.py +27 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/core/models.py +7 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/scheduler/__init__.py +1 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/scheduler/autorun.py +139 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/scheduler/windows.py +339 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/server/__init__.py +1 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/server/app.py +36 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/server/database.py +33 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/server/scheduler_router.py +53 -0
- dl2_tasks-0.1.0/src/datalys2_tasks/server/startup.py +65 -0
- dl2_tasks-0.1.0/src/dl2_tasks.egg-info/PKG-INFO +120 -0
- dl2_tasks-0.1.0/src/dl2_tasks.egg-info/SOURCES.txt +23 -0
- dl2_tasks-0.1.0/src/dl2_tasks.egg-info/dependency_links.txt +1 -0
- dl2_tasks-0.1.0/src/dl2_tasks.egg-info/entry_points.txt +2 -0
- dl2_tasks-0.1.0/src/dl2_tasks.egg-info/requires.txt +5 -0
- dl2_tasks-0.1.0/src/dl2_tasks.egg-info/top_level.txt +1 -0
dl2_tasks-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Datalys2 Tasks Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dl2_tasks-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dl2-tasks
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight local task orchestration system built on windows task scheduler
|
|
5
|
+
Author-email: Datalys2 Team <kameron@example.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Datalys2 Tasks Contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/yourusername/datalys2-tasks
|
|
29
|
+
Project-URL: Documentation, https://github.com/yourusername/datalys2-tasks/blob/main/DOCUMENTATION.md
|
|
30
|
+
Project-URL: Repository, https://github.com/yourusername/datalys2-tasks.git
|
|
31
|
+
Keywords: windows,task scheduler,automation,scheduling,cron
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Topic :: System :: Systems Administration
|
|
42
|
+
Requires-Python: >=3.8
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: fastapi>=0.100.0
|
|
46
|
+
Requires-Dist: uvicorn>=0.20.0
|
|
47
|
+
Requires-Dist: pydantic>=2.0.0
|
|
48
|
+
Requires-Dist: requests>=2.28.0
|
|
49
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# Datalys2 Tasks
|
|
53
|
+
|
|
54
|
+
**A lightweight, local task orchestration system built for Windows.**
|
|
55
|
+
|
|
56
|
+
`dl2-tasks` (Datalys2 Tasks) is a robust wrapper around the native **Windows Task Scheduler**. It provides a modern Python interface for scheduling, managing, and monitoring background tasks without the need for a heavy, always-on server process.
|
|
57
|
+
|
|
58
|
+
## 🌟 Features
|
|
59
|
+
|
|
60
|
+
- **No Background Process Required:** Leverages Windows' built-in `schtasks.exe`.
|
|
61
|
+
- **Self-Scheduling Scripts:** Add `schedule_me()` to your script, run it once, and it's scheduled forever.
|
|
62
|
+
- **Lightweight:** Minimal dependencies.
|
|
63
|
+
- **Robust:** If Python crashes, the scheduler remains.
|
|
64
|
+
|
|
65
|
+
## 📦 Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install dl2-tasks
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 🚀 Quick Start
|
|
72
|
+
|
|
73
|
+
### Auto-Scheduling (The "Magic" Way)
|
|
74
|
+
|
|
75
|
+
The easiest way to use Datalys2 Tasks is to let your scripts schedule themselves.
|
|
76
|
+
|
|
77
|
+
1. Create `my_task.py`:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import datetime
|
|
81
|
+
from datalys2_tasks.scheduler.autorun import schedule_me
|
|
82
|
+
|
|
83
|
+
def job():
|
|
84
|
+
with open("log.txt", "a") as f:
|
|
85
|
+
f.write(f"Ran at {datetime.datetime.now()}\n")
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
# Registers this script to run everyday at 08:00
|
|
89
|
+
schedule_me("MyDailyLog", frequency="DAILY", at="08:00")
|
|
90
|
+
|
|
91
|
+
job()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
2. Run it once manually:
|
|
95
|
+
```bash
|
|
96
|
+
python my_task.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
3. That's it! Windows will now run this script every day at 08:00.
|
|
100
|
+
|
|
101
|
+
### CLI Management
|
|
102
|
+
|
|
103
|
+
You can also manage tasks via the command line.
|
|
104
|
+
|
|
105
|
+
- **List tasks:** `datalys2-server schedule list`
|
|
106
|
+
- **Add a task:** `datalys2-server schedule add "Backup" "C:\scripts\backup.py" --schedule DAILY --time 02:00`
|
|
107
|
+
- **Run manually:** `datalys2-server schedule run "Backup"`
|
|
108
|
+
|
|
109
|
+
## 🖥️ Optional Dashboard
|
|
110
|
+
|
|
111
|
+
Includes a local dashboard for viewing task status.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
datalys2-server start
|
|
115
|
+
```
|
|
116
|
+
*Opens http://localhost:8000/dashboard*
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
For full details, see the documentation.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Datalys2 Tasks
|
|
2
|
+
|
|
3
|
+
**A lightweight, local task orchestration system built for Windows.**
|
|
4
|
+
|
|
5
|
+
`dl2-tasks` (Datalys2 Tasks) is a robust wrapper around the native **Windows Task Scheduler**. It provides a modern Python interface for scheduling, managing, and monitoring background tasks without the need for a heavy, always-on server process.
|
|
6
|
+
|
|
7
|
+
## 🌟 Features
|
|
8
|
+
|
|
9
|
+
- **No Background Process Required:** Leverages Windows' built-in `schtasks.exe`.
|
|
10
|
+
- **Self-Scheduling Scripts:** Add `schedule_me()` to your script, run it once, and it's scheduled forever.
|
|
11
|
+
- **Lightweight:** Minimal dependencies.
|
|
12
|
+
- **Robust:** If Python crashes, the scheduler remains.
|
|
13
|
+
|
|
14
|
+
## 📦 Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install dl2-tasks
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 🚀 Quick Start
|
|
21
|
+
|
|
22
|
+
### Auto-Scheduling (The "Magic" Way)
|
|
23
|
+
|
|
24
|
+
The easiest way to use Datalys2 Tasks is to let your scripts schedule themselves.
|
|
25
|
+
|
|
26
|
+
1. Create `my_task.py`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import datetime
|
|
30
|
+
from datalys2_tasks.scheduler.autorun import schedule_me
|
|
31
|
+
|
|
32
|
+
def job():
|
|
33
|
+
with open("log.txt", "a") as f:
|
|
34
|
+
f.write(f"Ran at {datetime.datetime.now()}\n")
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
# Registers this script to run everyday at 08:00
|
|
38
|
+
schedule_me("MyDailyLog", frequency="DAILY", at="08:00")
|
|
39
|
+
|
|
40
|
+
job()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
2. Run it once manually:
|
|
44
|
+
```bash
|
|
45
|
+
python my_task.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
3. That's it! Windows will now run this script every day at 08:00.
|
|
49
|
+
|
|
50
|
+
### CLI Management
|
|
51
|
+
|
|
52
|
+
You can also manage tasks via the command line.
|
|
53
|
+
|
|
54
|
+
- **List tasks:** `datalys2-server schedule list`
|
|
55
|
+
- **Add a task:** `datalys2-server schedule add "Backup" "C:\scripts\backup.py" --schedule DAILY --time 02:00`
|
|
56
|
+
- **Run manually:** `datalys2-server schedule run "Backup"`
|
|
57
|
+
|
|
58
|
+
## 🖥️ Optional Dashboard
|
|
59
|
+
|
|
60
|
+
Includes a local dashboard for viewing task status.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
datalys2-server start
|
|
64
|
+
```
|
|
65
|
+
*Opens http://localhost:8000/dashboard*
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
For full details, see the documentation.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dl2-tasks"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lightweight local task orchestration system built on windows task scheduler"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Datalys2 Team", email = "kameron@example.com"}
|
|
12
|
+
]
|
|
13
|
+
license = {file = "LICENSE"}
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Topic :: System :: Systems Administration",
|
|
25
|
+
]
|
|
26
|
+
keywords = ["windows", "task scheduler", "automation", "scheduling", "cron"]
|
|
27
|
+
requires-python = ">=3.8"
|
|
28
|
+
dependencies = [
|
|
29
|
+
"fastapi>=0.100.0",
|
|
30
|
+
"uvicorn>=0.20.0",
|
|
31
|
+
"pydantic>=2.0.0",
|
|
32
|
+
"requests>=2.28.0",
|
|
33
|
+
"sqlalchemy>=2.0.0"
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/yourusername/datalys2-tasks"
|
|
38
|
+
Documentation = "https://github.com/yourusername/datalys2-tasks/blob/main/DOCUMENTATION.md"
|
|
39
|
+
Repository = "https://github.com/yourusername/datalys2-tasks.git"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
datalys2-server = "datalys2_tasks.cli:main"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .server.app import start_server
|
|
5
|
+
from .server.startup import install_service, remove_service
|
|
6
|
+
from .scheduler.windows import WindowsTaskScheduler
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description="Datalys2 Tasks Server & Scheduler CLI")
|
|
10
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
11
|
+
|
|
12
|
+
# --- Server Commands ---
|
|
13
|
+
# Start Server
|
|
14
|
+
subparsers.add_parser("start", help="Start the Task Server immediately")
|
|
15
|
+
|
|
16
|
+
# Install Service
|
|
17
|
+
subparsers.add_parser("install", help="Register server as a Windows startup task")
|
|
18
|
+
|
|
19
|
+
# Remove Service
|
|
20
|
+
subparsers.add_parser("remove", help="Remove the Windows startup task")
|
|
21
|
+
|
|
22
|
+
# --- Scheduler Commands ---
|
|
23
|
+
sched_parser = subparsers.add_parser("schedule", help="Manage Windows Scheduled Tasks direct execution mode")
|
|
24
|
+
sched_subs = sched_parser.add_subparsers(dest="sched_command", help="Scheduler actions")
|
|
25
|
+
|
|
26
|
+
# Schedule: Create/Add
|
|
27
|
+
add_p = sched_subs.add_parser("add", help="Create a new scheduled task")
|
|
28
|
+
add_p.add_argument("name", help="Unique name for the task")
|
|
29
|
+
add_p.add_argument("script", help="Path to the Python script to execute")
|
|
30
|
+
add_p.add_argument("--schedule", default="DAILY", choices=["DAILY", "HOURLY", "MINUTE", "ONCE", "ONLOGON"], help="Schedule type")
|
|
31
|
+
add_p.add_argument("--time", help="Start time (HH:mm)")
|
|
32
|
+
add_p.add_argument("--interval", type=int, help="Interval (minutes) if schedule is MINUTE or modifier for others")
|
|
33
|
+
add_p.add_argument("--force", action="store_true", help="Overwrite existing task")
|
|
34
|
+
add_p.add_argument("--args", nargs="*", help="Arguments to pass to the script")
|
|
35
|
+
|
|
36
|
+
# Schedule: List
|
|
37
|
+
list_p = sched_subs.add_parser("list", help="List scheduled tasks")
|
|
38
|
+
list_p.add_argument("--pattern", default="*", help="Filter tasks by name (substring)")
|
|
39
|
+
|
|
40
|
+
# Schedule: Delete
|
|
41
|
+
del_p = sched_subs.add_parser("remove", help="Delete a scheduled task")
|
|
42
|
+
del_p.add_argument("name", help="Name of the task to delete")
|
|
43
|
+
|
|
44
|
+
# Schedule: Run (Manual)
|
|
45
|
+
run_p = sched_subs.add_parser("run", help="Manually run a scheduled task now")
|
|
46
|
+
run_p.add_argument("name", help="Name of the task to run")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
if args.command == "start":
|
|
52
|
+
print("Starting Datalys2 Server...")
|
|
53
|
+
start_server()
|
|
54
|
+
elif args.command == "install":
|
|
55
|
+
install_service()
|
|
56
|
+
elif args.command == "remove":
|
|
57
|
+
remove_service()
|
|
58
|
+
elif args.command == "schedule":
|
|
59
|
+
scheduler = WindowsTaskScheduler()
|
|
60
|
+
|
|
61
|
+
if args.sched_command == "add":
|
|
62
|
+
success = scheduler.create_task(
|
|
63
|
+
task_name=args.name,
|
|
64
|
+
script_path=args.script,
|
|
65
|
+
schedule=args.schedule,
|
|
66
|
+
start_time=args.time,
|
|
67
|
+
interval_minutes=args.interval,
|
|
68
|
+
args=args.args,
|
|
69
|
+
force=args.force
|
|
70
|
+
)
|
|
71
|
+
if success:
|
|
72
|
+
print(f"Task '{args.name}' scheduled successfully.")
|
|
73
|
+
else:
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
|
|
76
|
+
elif args.sched_command == "list":
|
|
77
|
+
tasks = scheduler.list_tasks(pattern=args.pattern)
|
|
78
|
+
if not tasks:
|
|
79
|
+
print("No matching tasks found.")
|
|
80
|
+
else:
|
|
81
|
+
print(f"{'Task Name':<40} {'Next Run Time':<25} {'Status':<15}")
|
|
82
|
+
print("-" * 80)
|
|
83
|
+
for t in tasks:
|
|
84
|
+
name = t.get('TaskName', 'N/A').replace('\\', '') # Cleanup backslashes often returned by schtasks
|
|
85
|
+
next_run = t.get('Next Run Time', 'N/A')
|
|
86
|
+
status = t.get('Status', 'N/A')
|
|
87
|
+
print(f"{name:<40} {next_run:<25} {status:<15}")
|
|
88
|
+
|
|
89
|
+
elif args.sched_command == "remove":
|
|
90
|
+
if scheduler.delete_task(args.name):
|
|
91
|
+
print(f"Task '{args.name}' deleted.")
|
|
92
|
+
else:
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
elif args.sched_command == "run":
|
|
96
|
+
if scheduler.run_task(args.name):
|
|
97
|
+
print(f"Task '{args.name}' triggered.")
|
|
98
|
+
else:
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
else:
|
|
101
|
+
sched_parser.print_help()
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
parser.print_help()
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Client decorators
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional, List, Union, Callable
|
|
6
|
+
from ..core.config import settings
|
|
7
|
+
from ..scheduler.windows import WindowsTaskScheduler
|
|
8
|
+
from ..server.database import SessionLocal, ScheduledTaskDB, init_db
|
|
9
|
+
|
|
10
|
+
class TaskWrapper:
|
|
11
|
+
def __init__(self, func, task_name: Optional[str] = None):
|
|
12
|
+
self.func = func
|
|
13
|
+
self.default_task_name = task_name or func.__name__
|
|
14
|
+
functools.update_wrapper(self, func)
|
|
15
|
+
|
|
16
|
+
# Determine the absolute path of the module defining the function
|
|
17
|
+
module = inspect.getmodule(func)
|
|
18
|
+
|
|
19
|
+
# Safely get __file__, defaulting to None if it doesn't exist
|
|
20
|
+
module_file = getattr(module, '__file__', None) if module else None
|
|
21
|
+
|
|
22
|
+
if module_file:
|
|
23
|
+
self.module_path = os.path.abspath(module_file)
|
|
24
|
+
else:
|
|
25
|
+
# Fallback (might fail in interactive shells)
|
|
26
|
+
self.module_path = os.path.abspath(sys.argv[0])
|
|
27
|
+
|
|
28
|
+
def __call__(self, *args, **kwargs):
|
|
29
|
+
"""Allow normal local execution."""
|
|
30
|
+
return self.func(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
def schedule_run(
|
|
33
|
+
self,
|
|
34
|
+
task_name: Optional[str] = None,
|
|
35
|
+
schedule: str = "DAILY",
|
|
36
|
+
start_time: Optional[str] = None,
|
|
37
|
+
interval: Optional[int] = None,
|
|
38
|
+
force: bool = False,
|
|
39
|
+
cli_args: Optional[List[str]] = None
|
|
40
|
+
) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Register this function's script as a Windows Scheduled Task.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
task_name (str, optional): Unique identifier for the Windows Task Scheduler.
|
|
46
|
+
Defaults to the name provided in @task decorator or function name.
|
|
47
|
+
schedule (str): Frequency ('DAILY', 'HOURLY', 'minute', 'ONCE', 'ONLOGON').
|
|
48
|
+
start_time (str): Time to start (HH:mm).
|
|
49
|
+
interval (int): Interval in minutes (if applicable).
|
|
50
|
+
force (bool): Overwrite existing task.
|
|
51
|
+
cli_args (List[str]): Command line arguments to pass to the script execution.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
bool: Success status.
|
|
55
|
+
"""
|
|
56
|
+
final_task_name = task_name or self.default_task_name
|
|
57
|
+
|
|
58
|
+
scheduler = WindowsTaskScheduler()
|
|
59
|
+
print(f"Scheduling script: {self.module_path} as '{final_task_name}'")
|
|
60
|
+
success = scheduler.create_task(
|
|
61
|
+
task_name=final_task_name,
|
|
62
|
+
script_path=self.module_path,
|
|
63
|
+
schedule=schedule,
|
|
64
|
+
start_time=start_time,
|
|
65
|
+
interval_minutes=interval,
|
|
66
|
+
args=cli_args,
|
|
67
|
+
force=force
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if success:
|
|
71
|
+
# Save to SQLite DB in %APPDATA%
|
|
72
|
+
try:
|
|
73
|
+
# Ensure DB tables exist (idempotent)
|
|
74
|
+
init_db()
|
|
75
|
+
|
|
76
|
+
db = SessionLocal()
|
|
77
|
+
# Check if exists to update or create
|
|
78
|
+
existing = db.query(ScheduledTaskDB).filter(ScheduledTaskDB.task_name == final_task_name).first()
|
|
79
|
+
if existing:
|
|
80
|
+
existing.script_path = self.module_path
|
|
81
|
+
existing.schedule_type = schedule
|
|
82
|
+
existing.schedule_time = start_time
|
|
83
|
+
existing.interval_minutes = interval
|
|
84
|
+
existing.description = f"Scheduled via decorator from {self.func.__name__}"
|
|
85
|
+
else:
|
|
86
|
+
new_task = ScheduledTaskDB(
|
|
87
|
+
task_name=final_task_name,
|
|
88
|
+
script_path=self.module_path,
|
|
89
|
+
schedule_type=schedule,
|
|
90
|
+
schedule_time=start_time,
|
|
91
|
+
interval_minutes=interval,
|
|
92
|
+
description=f"Scheduled via decorator from {self.func.__name__}"
|
|
93
|
+
)
|
|
94
|
+
db.add(new_task)
|
|
95
|
+
|
|
96
|
+
db.commit()
|
|
97
|
+
db.close()
|
|
98
|
+
print("Task registration saved to local database.")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print(f"Warning: Failed to save task to local database: {e}")
|
|
101
|
+
|
|
102
|
+
return success
|
|
103
|
+
|
|
104
|
+
def task(func: Union[Callable, None] = None, *, name: Optional[str] = None):
|
|
105
|
+
"""
|
|
106
|
+
Decorator to wrap a function as a Datalys2 task.
|
|
107
|
+
Usage:
|
|
108
|
+
@task
|
|
109
|
+
def my_func(): ...
|
|
110
|
+
|
|
111
|
+
@task(name="MyCustomTask")
|
|
112
|
+
def my_func(): ...
|
|
113
|
+
"""
|
|
114
|
+
if func is None:
|
|
115
|
+
return functools.partial(task, name=name)
|
|
116
|
+
return TaskWrapper(func, task_name=name)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Core shared modules
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
class Settings(BaseModel):
|
|
6
|
+
server_host: str = "127.0.0.1"
|
|
7
|
+
server_port: int = 8000
|
|
8
|
+
database_url: str = "" # Set in __init__ or defaulting
|
|
9
|
+
|
|
10
|
+
def __init__(self, **data):
|
|
11
|
+
super().__init__(**data)
|
|
12
|
+
if not self.database_url:
|
|
13
|
+
app_data = os.getenv('APPDATA') or os.path.expanduser('~')
|
|
14
|
+
app_dir = os.path.join(app_data, 'datalys2-tasks')
|
|
15
|
+
os.makedirs(app_dir, exist_ok=True)
|
|
16
|
+
db_path = os.path.join(app_dir, 'datalys2.db')
|
|
17
|
+
self.database_url = f"sqlite:///{db_path}"
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def load(cls, config_path: str = "datalys_config.json") -> "Settings":
|
|
21
|
+
if os.path.exists(config_path):
|
|
22
|
+
with open(config_path, "r") as f:
|
|
23
|
+
data = json.load(f)
|
|
24
|
+
return cls(**data)
|
|
25
|
+
return cls()
|
|
26
|
+
|
|
27
|
+
settings = Settings.load()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .windows import WindowsTaskScheduler
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
from .windows import WindowsTaskScheduler
|
|
6
|
+
|
|
7
|
+
# Use a logger instead of print where appropriate, but fallback to print for visibility in CLI usage
|
|
8
|
+
logger = logging.getLogger("datalys2.autorun")
|
|
9
|
+
|
|
10
|
+
def schedule_me(
|
|
11
|
+
name: str,
|
|
12
|
+
frequency: str = "DAILY",
|
|
13
|
+
at: Optional[str] = None,
|
|
14
|
+
interval: Optional[int] = None,
|
|
15
|
+
args: Optional[List[str]] = None,
|
|
16
|
+
overwrite: bool = False
|
|
17
|
+
) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Automatically schedules the current script as a Windows Task.
|
|
20
|
+
|
|
21
|
+
This function is designed to be the "one-line" setup for your periodic tasks.
|
|
22
|
+
It checks if the task is already scheduled (by name). If not, it creates it.
|
|
23
|
+
If it is, it does nothing (unless `overwrite=True`).
|
|
24
|
+
|
|
25
|
+
It is best placed at the very top of your main execution block.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> if __name__ == "__main__":
|
|
29
|
+
... from datalys2_tasks.scheduler import autorun
|
|
30
|
+
... # Run this script every day at 08:30 AM
|
|
31
|
+
... autorun.schedule_me("daily-report-job", at="08:30")
|
|
32
|
+
... # ... Rest of your script logic
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
name (str):
|
|
36
|
+
A unique name for this task. It will be stored in the Windows Task Scheduler
|
|
37
|
+
under the '\\datalys2\\' folder.
|
|
38
|
+
Example: "sales-report-generator"
|
|
39
|
+
|
|
40
|
+
frequency (str, optional):
|
|
41
|
+
How often the task should run.
|
|
42
|
+
Options:
|
|
43
|
+
- "DAILY": Runs once every day (default).
|
|
44
|
+
- "ONCE": Runs a single time (useful for deferred tasks).
|
|
45
|
+
- "ONLOGON": Runs when the user logs in.
|
|
46
|
+
- "MINUTE": Runs every `interval` minutes.
|
|
47
|
+
- "HOURLY": Runs every hour.
|
|
48
|
+
Defaults to "DAILY".
|
|
49
|
+
|
|
50
|
+
at (str, optional):
|
|
51
|
+
The start time for the schedule in "HH:MM" 24-hour format.
|
|
52
|
+
Required for "DAILY" and "ONCE" if you want a specific time.
|
|
53
|
+
If None, defaults to current time.
|
|
54
|
+
Example: "14:00" for 2:00 PM.
|
|
55
|
+
|
|
56
|
+
interval (int, optional):
|
|
57
|
+
Used when frequency is "MINUTE". Specifies the number of minutes between runs.
|
|
58
|
+
Example: 15 (run every 15 minutes).
|
|
59
|
+
|
|
60
|
+
args (List[str], optional):
|
|
61
|
+
A list of command-line arguments to pass to the script when the scheduler runs it.
|
|
62
|
+
Example: ["--mode", "production", "--verbose"]
|
|
63
|
+
|
|
64
|
+
overwrite (bool, optional):
|
|
65
|
+
If True, forces the task to be updated in the Windows Task Scheduler even if it
|
|
66
|
+
already exists. Use this if you've changed the schedule or arguments.
|
|
67
|
+
Defaults to False (safe mode).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
bool: True if the task is successfully scheduled (or was already there).
|
|
71
|
+
False if something went wrong during scheduling.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# 1. Normalize inputs
|
|
75
|
+
frequency = frequency.upper()
|
|
76
|
+
|
|
77
|
+
# 2. Initialize Scheduler Interface
|
|
78
|
+
scheduler = WindowsTaskScheduler()
|
|
79
|
+
|
|
80
|
+
# 3. Check for existing task
|
|
81
|
+
# The scheduler handles the folder prefix (e.g. \\datalys2\\) internal to query_task
|
|
82
|
+
print(f"✨ [Datalys2] Checking status for task: '{name}'")
|
|
83
|
+
existing_task = scheduler.query_task(name)
|
|
84
|
+
|
|
85
|
+
if existing_task and not overwrite:
|
|
86
|
+
print(f"✅ [Datalys2] Task '{name}' is already scheduled. Continuing execution...")
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
# 4. Prepare Context
|
|
90
|
+
# We need the absolute path to this script so the scheduler knows what to run.
|
|
91
|
+
# Note: This relies on sys.argv[0] being the script path.
|
|
92
|
+
script_path = os.path.abspath(sys.argv[0])
|
|
93
|
+
|
|
94
|
+
action_verb = "Updating" if existing_task else "Registering"
|
|
95
|
+
print(f"⚙️ [Datalys2] {action_verb} '{name}' to run {frequency}...")
|
|
96
|
+
if at:
|
|
97
|
+
print(f" 🕒 Time: {at}")
|
|
98
|
+
if interval:
|
|
99
|
+
print(f" ⏱️ Interval: {interval} minutes")
|
|
100
|
+
|
|
101
|
+
# 5. Execute Creation/Update
|
|
102
|
+
success = scheduler.create_task(
|
|
103
|
+
task_name=name,
|
|
104
|
+
script_path=script_path,
|
|
105
|
+
schedule=frequency,
|
|
106
|
+
start_time=at,
|
|
107
|
+
interval_minutes=interval,
|
|
108
|
+
args=args,
|
|
109
|
+
force=True # We force here because we've already decided to update/create
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if success:
|
|
113
|
+
print(f"🚀 [Datalys2] Success! '{name}' is scheduled.")
|
|
114
|
+
else:
|
|
115
|
+
print(f"❌ [Datalys2] Failed to schedule '{name}'. Check permissions or logs.")
|
|
116
|
+
|
|
117
|
+
return success
|
|
118
|
+
|
|
119
|
+
# Alias for backward compatibility
|
|
120
|
+
def ensure_task(
|
|
121
|
+
task_name: str,
|
|
122
|
+
schedule: str = "DAILY",
|
|
123
|
+
start_time: Optional[str] = None,
|
|
124
|
+
interval_minutes: Optional[int] = None,
|
|
125
|
+
script_args: Optional[List[str]] = None,
|
|
126
|
+
force_update: bool = False
|
|
127
|
+
) -> bool:
|
|
128
|
+
"""
|
|
129
|
+
Deprecated alias for `schedule_me`.
|
|
130
|
+
Please use `datalys2_tasks.scheduler.autorun.schedule_me` instead.
|
|
131
|
+
"""
|
|
132
|
+
return schedule_me(
|
|
133
|
+
name=task_name,
|
|
134
|
+
frequency=schedule,
|
|
135
|
+
at=start_time,
|
|
136
|
+
interval=interval_minutes,
|
|
137
|
+
args=script_args,
|
|
138
|
+
overwrite=force_update
|
|
139
|
+
)
|