wdecorators 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.
- wdecorators-0.1.0/LICENSE +21 -0
- wdecorators-0.1.0/MANIFEST.in +2 -0
- wdecorators-0.1.0/PKG-INFO +42 -0
- wdecorators-0.1.0/README.md +1 -0
- wdecorators-0.1.0/pyproject.toml +23 -0
- wdecorators-0.1.0/setup.cfg +7 -0
- wdecorators-0.1.0/setup.py +38 -0
- wdecorators-0.1.0/wdecorators/__init__.py +1 -0
- wdecorators-0.1.0/wdecorators/periodic_scheduller/__init__.py +1 -0
- wdecorators-0.1.0/wdecorators/periodic_scheduller/controller.py +307 -0
- wdecorators-0.1.0/wdecorators.egg-info/PKG-INFO +42 -0
- wdecorators-0.1.0/wdecorators.egg-info/SOURCES.txt +13 -0
- wdecorators-0.1.0/wdecorators.egg-info/dependency_links.txt +1 -0
- wdecorators-0.1.0/wdecorators.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 William Rodriguez
|
|
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.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: wdecorators
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Decorators for Python
|
|
5
|
+
Home-page: https://github.com/wisrovi/wdecorators
|
|
6
|
+
Author: William Steve Rodriguez Villamizar
|
|
7
|
+
Author-email: wisrovi <wisrovi.rodriguez@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 William Rodriguez
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/wisrovi/wdecorators
|
|
31
|
+
Project-URL: Bug Tracker, https://github.com/wisrovi/wdecorators/issues
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Requires-Python: >=3.6
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: home-page
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
|
|
42
|
+
# wdecorators
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# wdecorators
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wdecorators"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="wisrovi", email="wisrovi.rodriguez@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Decorators for Python"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = { file="LICENSE" }
|
|
14
|
+
requires-python = ">=3.6"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
"Homepage" = "https://github.com/wisrovi/wdecorators"
|
|
23
|
+
"Bug Tracker" = "https://github.com/wisrovi/wdecorators/issues"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
try:
|
|
2
|
+
import tomllib # Python 3.11+
|
|
3
|
+
except ModuleNotFoundError:
|
|
4
|
+
# pip install tomli
|
|
5
|
+
import tomli as tomllib # Python <3.11
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from setuptools import setup, find_packages
|
|
9
|
+
|
|
10
|
+
with open("pyproject.toml", "rb") as archivo:
|
|
11
|
+
config_project = tomllib.load(archivo)
|
|
12
|
+
|
|
13
|
+
this_directory = Path(__file__).parent
|
|
14
|
+
long_description = (this_directory / "README.md").read_text()
|
|
15
|
+
|
|
16
|
+
setup(
|
|
17
|
+
name=config_project["project"]["name"], # Nombre del paquete en PyPI
|
|
18
|
+
version=config_project["project"]["version"],
|
|
19
|
+
packages=find_packages(),
|
|
20
|
+
install_requires=[
|
|
21
|
+
"loguru>=0.7.0",
|
|
22
|
+
],
|
|
23
|
+
classifiers=[
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Topic :: Software Development :: Build Tools",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
],
|
|
30
|
+
description=config_project["project"]["description"],
|
|
31
|
+
long_description_content_type="text/markdown",
|
|
32
|
+
long_description=long_description,
|
|
33
|
+
url="https://github.com/wisrovi/wdecorators",
|
|
34
|
+
author="William Steve Rodriguez Villamizar",
|
|
35
|
+
author_email="wisrovi.rodriguez@gmail.com",
|
|
36
|
+
license="MIT",
|
|
37
|
+
python_requires=">=3.6", # Requiere Python >=3.6 y <3.10
|
|
38
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .periodic_scheduller import Periodic_task_sched
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .controller import Periodic_task_sched
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import time
|
|
3
|
+
import psutil
|
|
4
|
+
import threading
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
import functools
|
|
7
|
+
import json
|
|
8
|
+
import uvicorn
|
|
9
|
+
from fastapi import FastAPI, HTTPException, Depends, Request, Form
|
|
10
|
+
from fastapi.responses import HTMLResponse
|
|
11
|
+
from fastapi.templating import Jinja2Templates
|
|
12
|
+
from fastapi.staticfiles import StaticFiles
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from typing import Optional
|
|
15
|
+
import jwt
|
|
16
|
+
import socket
|
|
17
|
+
|
|
18
|
+
# Configuración de autenticación (clave secreta para JWT)
|
|
19
|
+
SECRET_KEY = "mysecretkey"
|
|
20
|
+
ALGORITHM = "HS256"
|
|
21
|
+
ADMIN_TOKEN = jwt.encode({"role": "admin"}, SECRET_KEY, algorithm=ALGORITHM)
|
|
22
|
+
|
|
23
|
+
# Archivo de base de datos SQLite
|
|
24
|
+
DB_FILE = "task_logs_v14.db"
|
|
25
|
+
|
|
26
|
+
class DatabaseHandler:
|
|
27
|
+
"""Maneja la conexión con SQLite y PostgreSQL, usando lock para serializar operaciones."""
|
|
28
|
+
def __init__(self, db_config=None):
|
|
29
|
+
self.use_postgres = False
|
|
30
|
+
self.lock = threading.Lock()
|
|
31
|
+
|
|
32
|
+
if db_config:
|
|
33
|
+
try:
|
|
34
|
+
import psycopg2
|
|
35
|
+
self.conn = psycopg2.connect(**db_config)
|
|
36
|
+
self.use_postgres = True
|
|
37
|
+
print("Conectado a PostgreSQL.")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(f"Error con PostgreSQL: {e}. Usando SQLite.")
|
|
40
|
+
self.conn = sqlite3.connect(DB_FILE, check_same_thread=False)
|
|
41
|
+
self.cursor = self.conn.cursor() # Cursor no se usará directamente
|
|
42
|
+
else:
|
|
43
|
+
self.conn = sqlite3.connect(DB_FILE, check_same_thread=False)
|
|
44
|
+
self.cursor = self.conn.cursor()
|
|
45
|
+
print("Usando SQLite.")
|
|
46
|
+
|
|
47
|
+
self.setup_database()
|
|
48
|
+
|
|
49
|
+
def setup_database(self):
|
|
50
|
+
"""Crea las tablas necesarias."""
|
|
51
|
+
queries = [
|
|
52
|
+
"""
|
|
53
|
+
CREATE TABLE IF NOT EXISTS logs (
|
|
54
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
55
|
+
timestamp TEXT,
|
|
56
|
+
task_name TEXT,
|
|
57
|
+
message TEXT
|
|
58
|
+
)
|
|
59
|
+
""",
|
|
60
|
+
"""
|
|
61
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
62
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
name TEXT UNIQUE,
|
|
64
|
+
interval INTEGER,
|
|
65
|
+
priority TEXT,
|
|
66
|
+
status TEXT,
|
|
67
|
+
max_executions INTEGER,
|
|
68
|
+
current_executions INTEGER,
|
|
69
|
+
dynamic_interval BOOLEAN,
|
|
70
|
+
adaptive_priority BOOLEAN,
|
|
71
|
+
dependent_task TEXT,
|
|
72
|
+
enable_api BOOLEAN
|
|
73
|
+
)
|
|
74
|
+
"""
|
|
75
|
+
]
|
|
76
|
+
with self.lock:
|
|
77
|
+
cur = self.conn.cursor()
|
|
78
|
+
for query in queries:
|
|
79
|
+
cur.execute(query)
|
|
80
|
+
self.conn.commit()
|
|
81
|
+
cur.close()
|
|
82
|
+
|
|
83
|
+
def execute(self, query, params=()):
|
|
84
|
+
"""Ejecuta una consulta en la base de datos usando un cursor nuevo."""
|
|
85
|
+
with self.lock:
|
|
86
|
+
cur = self.conn.cursor()
|
|
87
|
+
cur.execute(query, params)
|
|
88
|
+
self.conn.commit()
|
|
89
|
+
cur.close()
|
|
90
|
+
|
|
91
|
+
def fetch_all(self, query, params=()):
|
|
92
|
+
"""Obtiene todos los resultados usando un cursor nuevo."""
|
|
93
|
+
with self.lock:
|
|
94
|
+
cur = self.conn.cursor()
|
|
95
|
+
cur.execute(query, params)
|
|
96
|
+
results = cur.fetchall()
|
|
97
|
+
cur.close()
|
|
98
|
+
return results
|
|
99
|
+
|
|
100
|
+
class Periodic_task_sched:
|
|
101
|
+
"""Clase para manejar tareas periódicas con decoradores, API opcional y panel de control."""
|
|
102
|
+
_api_running = False # Evitar múltiples inicios de la API
|
|
103
|
+
|
|
104
|
+
def __init__(self):
|
|
105
|
+
self.database = None
|
|
106
|
+
self.executors = {}
|
|
107
|
+
self.api_enabled = False
|
|
108
|
+
self.api_app = FastAPI()
|
|
109
|
+
|
|
110
|
+
# Montar archivos estáticos y plantillas HTML
|
|
111
|
+
self.api_app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
112
|
+
self.templates = Jinja2Templates(directory="templates")
|
|
113
|
+
|
|
114
|
+
# Panel Web de Administración con métricas en tiempo real
|
|
115
|
+
@self.api_app.get("/", response_class=HTMLResponse)
|
|
116
|
+
def dashboard(request: Request):
|
|
117
|
+
tasks = list(self.executors.keys())
|
|
118
|
+
logs = self.database.fetch_all("SELECT * FROM logs ORDER BY timestamp DESC LIMIT 50")
|
|
119
|
+
return self.templates.TemplateResponse("dashboard.html", {"request": request, "tasks": tasks, "logs": logs})
|
|
120
|
+
|
|
121
|
+
# Rutas API protegidas (usando Form para recibir datos de formularios)
|
|
122
|
+
@self.api_app.post("/list_tasks/")
|
|
123
|
+
def list_tasks(token: str = Form(...)):
|
|
124
|
+
self.verify_admin(token)
|
|
125
|
+
return list(self.executors.keys())
|
|
126
|
+
|
|
127
|
+
@self.api_app.post("/pause_task/")
|
|
128
|
+
def pause_task(task_name: str = Form(...), token: str = Form(...)):
|
|
129
|
+
self.verify_admin(token)
|
|
130
|
+
if task_name in self.executors:
|
|
131
|
+
self.executors[task_name].pause()
|
|
132
|
+
return {"status": f"Tarea {task_name} pausada."}
|
|
133
|
+
return {"error": f"Tarea {task_name} no encontrada."}
|
|
134
|
+
|
|
135
|
+
@self.api_app.post("/resume_task/")
|
|
136
|
+
def resume_task(task_name: str = Form(...), token: str = Form(...)):
|
|
137
|
+
self.verify_admin(token)
|
|
138
|
+
if task_name in self.executors:
|
|
139
|
+
self.executors[task_name].resume()
|
|
140
|
+
return {"status": f"Tarea {task_name} reanudada."}
|
|
141
|
+
return {"error": f"Tarea {task_name} no encontrada."}
|
|
142
|
+
|
|
143
|
+
@self.api_app.post("/stop_task/")
|
|
144
|
+
def stop_task(task_name: str = Form(...), token: str = Form(...)):
|
|
145
|
+
self.verify_admin(token)
|
|
146
|
+
if task_name in self.executors:
|
|
147
|
+
self.executors[task_name].stop()
|
|
148
|
+
return {"status": f"Tarea {task_name} detenida."}
|
|
149
|
+
return {"error": f"Tarea {task_name} no encontrada."}
|
|
150
|
+
|
|
151
|
+
# Protección contra código malicioso: Ejecuta código Python para crear tareas
|
|
152
|
+
@self.api_app.post("/execute_python_task/")
|
|
153
|
+
def execute_python_task(task_code: str = Form(...), interval: int = Form(...), priority: str = Form(...), token: str = Form(...)):
|
|
154
|
+
self.verify_admin(token)
|
|
155
|
+
exec_globals = {}
|
|
156
|
+
safe_env = {"print": print, "range": range, "len": len}
|
|
157
|
+
try:
|
|
158
|
+
exec(task_code, safe_env, exec_globals)
|
|
159
|
+
if "task_function" in exec_globals:
|
|
160
|
+
new_task = self.periodic_execution(interval, priority)(exec_globals["task_function"])
|
|
161
|
+
new_task()
|
|
162
|
+
return {"status": "Nueva tarea creada y en ejecución."}
|
|
163
|
+
return {"error": "No se encontró 'task_function' en el código proporcionado."}
|
|
164
|
+
except Exception as e:
|
|
165
|
+
return {"error": str(e)}
|
|
166
|
+
|
|
167
|
+
# Ruta para mostrar el formulario de login
|
|
168
|
+
@self.api_app.get("/login", response_class=HTMLResponse)
|
|
169
|
+
def login_page(request: Request):
|
|
170
|
+
return self.templates.TemplateResponse("login.html", {"request": request})
|
|
171
|
+
|
|
172
|
+
# Ruta para procesar el login y devolver el token
|
|
173
|
+
@self.api_app.post("/login")
|
|
174
|
+
async def login(request: Request):
|
|
175
|
+
form_data = await request.form()
|
|
176
|
+
username = form_data.get("username")
|
|
177
|
+
password = form_data.get("password")
|
|
178
|
+
|
|
179
|
+
# Aquí definimos la lógica de autenticación.
|
|
180
|
+
# Por ejemplo, para efectos de prueba:
|
|
181
|
+
if username == "admin" and password == "admin":
|
|
182
|
+
role = "admin"
|
|
183
|
+
else:
|
|
184
|
+
# Si no es admin, se asigna rol de supervisor (o se rechaza el login según tus requisitos)
|
|
185
|
+
role = "supervisor"
|
|
186
|
+
|
|
187
|
+
token = jwt.encode({"role": role}, SECRET_KEY, algorithm=ALGORITHM)
|
|
188
|
+
# Redireccionar al dashboard incluyendo el token en la URL o devolverlo en la respuesta.
|
|
189
|
+
# Aquí devolvemos el token en una respuesta JSON para simplicidad.
|
|
190
|
+
return {"token": token}
|
|
191
|
+
|
|
192
|
+
def verify_admin(self, token: str):
|
|
193
|
+
"""Verifica si el usuario es administrador o supervisor (por defecto, sin login, es supervisor)."""
|
|
194
|
+
try:
|
|
195
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
196
|
+
if payload["role"] not in ["admin", "supervisor"]:
|
|
197
|
+
raise HTTPException(status_code=403, detail="Acceso denegado.")
|
|
198
|
+
except jwt.PyJWTError:
|
|
199
|
+
raise HTTPException(status_code=401, detail="Token inválido.")
|
|
200
|
+
return token
|
|
201
|
+
|
|
202
|
+
def set_database(self, db_config_json=None):
|
|
203
|
+
"""Configura la base de datos (SQLite o PostgreSQL)."""
|
|
204
|
+
db_config = json.loads(db_config_json) if db_config_json else None
|
|
205
|
+
self.database = DatabaseHandler(db_config)
|
|
206
|
+
|
|
207
|
+
def periodic_execution(self, interval, priority="media", max_executions=None, dynamic_interval=False, adaptive_priority=False, dependent_task=None, enable_api=False):
|
|
208
|
+
"""Decorador para manejar tareas periódicas con configuración avanzada."""
|
|
209
|
+
if enable_api:
|
|
210
|
+
self.api_enabled = True
|
|
211
|
+
|
|
212
|
+
def decorator(func):
|
|
213
|
+
executor = self.PeriodicExecutor(func, interval, priority, max_executions, dynamic_interval, adaptive_priority, dependent_task, enable_api, self.database)
|
|
214
|
+
self.executors[func.__name__] = executor
|
|
215
|
+
|
|
216
|
+
@functools.wraps(func)
|
|
217
|
+
def wrapper(*args, **kwargs):
|
|
218
|
+
executor.start(*args, **kwargs)
|
|
219
|
+
return executor
|
|
220
|
+
return wrapper
|
|
221
|
+
return decorator
|
|
222
|
+
|
|
223
|
+
def is_port_in_use(self, port=8000):
|
|
224
|
+
"""Verifica si el puerto ya está en uso."""
|
|
225
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
226
|
+
return s.connect_ex(("localhost", port)) == 0
|
|
227
|
+
|
|
228
|
+
def start_api(self):
|
|
229
|
+
"""Inicia la API solo si es requerida y no está en ejecución."""
|
|
230
|
+
if self.api_enabled and not self.is_port_in_use():
|
|
231
|
+
if not Periodic_task_sched._api_running:
|
|
232
|
+
print("Iniciando API en http://localhost:8000")
|
|
233
|
+
Periodic_task_sched._api_running = True
|
|
234
|
+
threading.Thread(target=uvicorn.run, args=(self.api_app,), kwargs={"host": "0.0.0.0", "port": 8000}, daemon=True).start()
|
|
235
|
+
else:
|
|
236
|
+
print("La API ya está en ejecución en otro proceso. Se reutiliza la instancia existente.")
|
|
237
|
+
|
|
238
|
+
class PeriodicExecutor:
|
|
239
|
+
"""Ejecutor de tareas periódicas con prioridad inteligente y dependencias."""
|
|
240
|
+
def __init__(self, func, interval, priority, max_executions, dynamic_interval, adaptive_priority, dependent_task, enable_api, database):
|
|
241
|
+
self.func = func
|
|
242
|
+
self.interval = interval
|
|
243
|
+
self.priority = priority
|
|
244
|
+
self.max_executions = max_executions or float("inf")
|
|
245
|
+
self.execution_count = 0
|
|
246
|
+
self.running = False
|
|
247
|
+
self.paused = False
|
|
248
|
+
self.dynamic_interval = dynamic_interval
|
|
249
|
+
self.adaptive_priority = adaptive_priority
|
|
250
|
+
self.dependent_task = dependent_task
|
|
251
|
+
self.enable_api = enable_api
|
|
252
|
+
self.database = database
|
|
253
|
+
|
|
254
|
+
self._save_task()
|
|
255
|
+
|
|
256
|
+
def _save_task(self):
|
|
257
|
+
"""Guarda la tarea en la base de datos."""
|
|
258
|
+
self.database.execute(
|
|
259
|
+
"INSERT OR IGNORE INTO tasks (name, interval, priority, status, max_executions, current_executions, dynamic_interval, adaptive_priority, dependent_task, enable_api) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
260
|
+
(self.func.__name__, self.interval, self.priority, "stopped", self.max_executions, 0, self.dynamic_interval, self.adaptive_priority, self.dependent_task, self.enable_api)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def start(self):
|
|
264
|
+
"""Inicia la ejecución de la tarea."""
|
|
265
|
+
if not self.running:
|
|
266
|
+
self.running = True
|
|
267
|
+
threading.Thread(target=self._run).start()
|
|
268
|
+
|
|
269
|
+
def _run(self):
|
|
270
|
+
"""Ejecuta la tarea según configuraciones."""
|
|
271
|
+
while self.running and self.execution_count < self.max_executions:
|
|
272
|
+
if self.paused:
|
|
273
|
+
time.sleep(1)
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
# Si existe dependencia, verificar que la tarea dependiente esté completada
|
|
277
|
+
if self.dependent_task:
|
|
278
|
+
result = self.database.fetch_all("SELECT status FROM tasks WHERE name = ?", (self.dependent_task,))
|
|
279
|
+
if result and result[0][0] != "completed":
|
|
280
|
+
time.sleep(self.interval)
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
self.func()
|
|
284
|
+
self.execution_count += 1
|
|
285
|
+
|
|
286
|
+
self.database.execute(
|
|
287
|
+
"INSERT INTO logs (timestamp, task_name, message) VALUES (?, ?, ?)",
|
|
288
|
+
(datetime.now().isoformat(), self.func.__name__, f"Ejecutado {self.execution_count} veces.")
|
|
289
|
+
)
|
|
290
|
+
self.database.execute(
|
|
291
|
+
"UPDATE tasks SET status = 'completed' WHERE name = ?",
|
|
292
|
+
(self.func.__name__,)
|
|
293
|
+
)
|
|
294
|
+
time.sleep(self.interval)
|
|
295
|
+
self.running = False
|
|
296
|
+
|
|
297
|
+
def stop(self):
|
|
298
|
+
"""Detiene la ejecución de la tarea."""
|
|
299
|
+
self.running = False
|
|
300
|
+
|
|
301
|
+
def pause(self):
|
|
302
|
+
"""Pausa temporalmente la ejecución de la tarea."""
|
|
303
|
+
self.paused = True
|
|
304
|
+
|
|
305
|
+
def resume(self):
|
|
306
|
+
"""Reanuda la tarea pausada."""
|
|
307
|
+
self.paused = False
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: wdecorators
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Decorators for Python
|
|
5
|
+
Home-page: https://github.com/wisrovi/wdecorators
|
|
6
|
+
Author: William Steve Rodriguez Villamizar
|
|
7
|
+
Author-email: wisrovi <wisrovi.rodriguez@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 William Rodriguez
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/wisrovi/wdecorators
|
|
31
|
+
Project-URL: Bug Tracker, https://github.com/wisrovi/wdecorators/issues
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Requires-Python: >=3.6
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: home-page
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
|
|
42
|
+
# wdecorators
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.cfg
|
|
6
|
+
setup.py
|
|
7
|
+
wdecorators/__init__.py
|
|
8
|
+
wdecorators.egg-info/PKG-INFO
|
|
9
|
+
wdecorators.egg-info/SOURCES.txt
|
|
10
|
+
wdecorators.egg-info/dependency_links.txt
|
|
11
|
+
wdecorators.egg-info/top_level.txt
|
|
12
|
+
wdecorators/periodic_scheduller/__init__.py
|
|
13
|
+
wdecorators/periodic_scheduller/controller.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wdecorators
|