nano-wait 4.0.4__py3-none-any.whl
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.
- nano_wait/__init__.py +4 -0
- nano_wait/__main__.py +4 -0
- nano_wait/cli.py +137 -0
- nano_wait/core.py +159 -0
- nano_wait/dashboard.py +132 -0
- nano_wait/exceptions.py +2 -0
- nano_wait/explain.py +47 -0
- nano_wait/nano_wait.py +218 -0
- nano_wait/telemetry.py +77 -0
- nano_wait/utils.py +13 -0
- nano_wait/vision.py +4 -0
- nano_wait-4.0.4.dist-info/METADATA +380 -0
- nano_wait-4.0.4.dist-info/RECORD +17 -0
- nano_wait-4.0.4.dist-info/WHEEL +5 -0
- nano_wait-4.0.4.dist-info/entry_points.txt +2 -0
- nano_wait-4.0.4.dist-info/licenses/LICENSE +21 -0
- nano_wait-4.0.4.dist-info/top_level.txt +1 -0
nano_wait/__init__.py
ADDED
nano_wait/__main__.py
ADDED
nano_wait/cli.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# cli.py
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from .nano_wait import wait
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
description="Nano-Wait — Adaptive smart wait for Python."
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# ------------------------
|
|
14
|
+
# Core arguments
|
|
15
|
+
# ------------------------
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"time",
|
|
18
|
+
type=float,
|
|
19
|
+
help="Base time in seconds"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--wifi",
|
|
24
|
+
type=str,
|
|
25
|
+
help="Wi-Fi SSID (optional)"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--speed",
|
|
30
|
+
type=str,
|
|
31
|
+
default="normal",
|
|
32
|
+
help="slow | normal | fast | ultra | numeric value"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--smart",
|
|
37
|
+
action="store_true",
|
|
38
|
+
help="Enable Smart Context Mode (auto speed)"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--verbose",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="Show debug output"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--log",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Write log file (nano_wait.log)"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# ------------------------
|
|
54
|
+
# Explain mode
|
|
55
|
+
# ------------------------
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--explain",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="Explain how the wait time was calculated"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# ------------------------
|
|
63
|
+
# Local Telemetry (opt-in)
|
|
64
|
+
# ------------------------
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--telemetry",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Enable local experimental telemetry (no remote collection)"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# ------------------------
|
|
72
|
+
# Headless mode (macOS safe)
|
|
73
|
+
# ------------------------
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--headless",
|
|
76
|
+
action="store_true",
|
|
77
|
+
help="Disable any UI / Tk / dashboard (recommended on macOS & CI)"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# ------------------------
|
|
81
|
+
# Execution Profile
|
|
82
|
+
# ------------------------
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--profile",
|
|
85
|
+
type=str,
|
|
86
|
+
choices=["ci", "testing", "rpa"],
|
|
87
|
+
help="Execution profile to adjust wait behavior"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
args = parser.parse_args()
|
|
91
|
+
|
|
92
|
+
# ------------------------
|
|
93
|
+
# Force headless behavior
|
|
94
|
+
# ------------------------
|
|
95
|
+
if args.headless:
|
|
96
|
+
# Environment flag that NanoWait (and submodules) can read
|
|
97
|
+
os.environ["NANOWAIT_HEADLESS"] = "1"
|
|
98
|
+
|
|
99
|
+
# Safety: disable telemetry UI automatically
|
|
100
|
+
if args.telemetry:
|
|
101
|
+
print("[NanoWait] Headless mode enabled → telemetry UI disabled")
|
|
102
|
+
args.telemetry = False
|
|
103
|
+
|
|
104
|
+
# ------------------------
|
|
105
|
+
# Execute NanoWait
|
|
106
|
+
# ------------------------
|
|
107
|
+
result = wait(
|
|
108
|
+
t=args.time,
|
|
109
|
+
wifi=args.wifi,
|
|
110
|
+
speed=args.speed,
|
|
111
|
+
smart=args.smart,
|
|
112
|
+
verbose=args.verbose,
|
|
113
|
+
log=args.log,
|
|
114
|
+
explain=args.explain,
|
|
115
|
+
telemetry=args.telemetry,
|
|
116
|
+
profile=args.profile
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# ------------------------
|
|
120
|
+
# Output explain
|
|
121
|
+
# ------------------------
|
|
122
|
+
if args.explain:
|
|
123
|
+
print("\n--- NanoWait Explain Report ---")
|
|
124
|
+
|
|
125
|
+
if isinstance(result, tuple):
|
|
126
|
+
report, telemetry = result
|
|
127
|
+
print(report.explain())
|
|
128
|
+
|
|
129
|
+
if telemetry is not None:
|
|
130
|
+
print("\n--- Telemetry Summary ---")
|
|
131
|
+
print(telemetry)
|
|
132
|
+
else:
|
|
133
|
+
print(result.explain())
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
nano_wait/core.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# core.py
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class ExecutionProfile:
|
|
7
|
+
name: str
|
|
8
|
+
aggressiveness: float # multiplicador para intervalos adaptativos
|
|
9
|
+
tolerance: float # permissividade a falhas temporárias
|
|
10
|
+
poll_interval: float # base para time.sleep
|
|
11
|
+
verbose: bool # mostra debug automaticamente
|
|
12
|
+
|
|
13
|
+
# Perfis padrão
|
|
14
|
+
PROFILES = {
|
|
15
|
+
"ci": ExecutionProfile("ci", aggressiveness=0.5, tolerance=0.9, poll_interval=0.05, verbose=True),
|
|
16
|
+
"testing": ExecutionProfile("testing", aggressiveness=1.0, tolerance=0.7, poll_interval=0.1, verbose=True),
|
|
17
|
+
"rpa": ExecutionProfile("rpa", aggressiveness=2.0, tolerance=0.5, poll_interval=0.2, verbose=False),
|
|
18
|
+
"default": ExecutionProfile("default", aggressiveness=1.0, tolerance=0.8, poll_interval=0.1, verbose=False)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class NanoWait:
|
|
22
|
+
def __init__(self, profile: Optional[str] = None):
|
|
23
|
+
import platform
|
|
24
|
+
self.system = platform.system().lower()
|
|
25
|
+
self.profile = PROFILES.get(profile, PROFILES["default"])
|
|
26
|
+
|
|
27
|
+
# Wi-Fi setup
|
|
28
|
+
if self.system == "windows":
|
|
29
|
+
try:
|
|
30
|
+
import pywifi
|
|
31
|
+
self.wifi = pywifi.PyWiFi()
|
|
32
|
+
self.interface = self.wifi.interfaces()[0]
|
|
33
|
+
except Exception:
|
|
34
|
+
self.wifi = None
|
|
35
|
+
self.interface = None
|
|
36
|
+
else:
|
|
37
|
+
self.wifi = None
|
|
38
|
+
self.interface = None
|
|
39
|
+
|
|
40
|
+
# ------------------------
|
|
41
|
+
# Context collection
|
|
42
|
+
# ------------------------
|
|
43
|
+
|
|
44
|
+
def get_pc_score(self) -> float:
|
|
45
|
+
import psutil
|
|
46
|
+
try:
|
|
47
|
+
cpu = psutil.cpu_percent(interval=1)
|
|
48
|
+
mem = psutil.virtual_memory().percent
|
|
49
|
+
cpu_score = max(0, min(10, 10 - cpu / 10))
|
|
50
|
+
mem_score = max(0, min(10, 10 - mem / 10))
|
|
51
|
+
return round((cpu_score + mem_score) / 2, 2)
|
|
52
|
+
except Exception:
|
|
53
|
+
return 5.0
|
|
54
|
+
|
|
55
|
+
def get_wifi_signal(self, ssid: str | None = None) -> float:
|
|
56
|
+
try:
|
|
57
|
+
if self.system == "windows" and self.interface:
|
|
58
|
+
self.interface.scan()
|
|
59
|
+
import time
|
|
60
|
+
time.sleep(2)
|
|
61
|
+
for net in self.interface.scan_results():
|
|
62
|
+
if ssid is None or net.ssid == ssid:
|
|
63
|
+
return max(0, min(10, (net.signal + 100) / 10))
|
|
64
|
+
|
|
65
|
+
elif self.system == "darwin":
|
|
66
|
+
import subprocess
|
|
67
|
+
out = subprocess.check_output(
|
|
68
|
+
[
|
|
69
|
+
"/System/Library/PrivateFrameworks/Apple80211.framework/"
|
|
70
|
+
"Versions/Current/Resources/airport",
|
|
71
|
+
"-I"
|
|
72
|
+
],
|
|
73
|
+
text=True
|
|
74
|
+
)
|
|
75
|
+
line = [l for l in out.splitlines() if "agrCtlRSSI" in l][0]
|
|
76
|
+
rssi = int(line.split(":")[1].strip())
|
|
77
|
+
return max(0, min(10, (rssi + 100) / 10))
|
|
78
|
+
|
|
79
|
+
elif self.system == "linux":
|
|
80
|
+
import subprocess
|
|
81
|
+
out = subprocess.check_output(
|
|
82
|
+
["nmcli", "-t", "-f", "ACTIVE,SSID,SIGNAL", "dev", "wifi"],
|
|
83
|
+
text=True
|
|
84
|
+
)
|
|
85
|
+
for l in out.splitlines():
|
|
86
|
+
active, name, sig = l.split(":")
|
|
87
|
+
if active == "yes" or (ssid and name == ssid):
|
|
88
|
+
return max(0, min(10, int(sig) / 10))
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
return 5.0
|
|
93
|
+
|
|
94
|
+
def snapshot_context(self, ssid: str | None = None) -> dict:
|
|
95
|
+
"""
|
|
96
|
+
Captures an immutable snapshot of system context.
|
|
97
|
+
This is the foundation for deterministic explain mode.
|
|
98
|
+
"""
|
|
99
|
+
pc = self.get_pc_score()
|
|
100
|
+
wifi = self.get_wifi_signal(ssid) if ssid else None
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"pc_score": pc,
|
|
104
|
+
"wifi_score": wifi
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# ------------------------
|
|
108
|
+
# Smart Context
|
|
109
|
+
# ------------------------
|
|
110
|
+
|
|
111
|
+
def smart_speed(self, ssid: str | None = None) -> float:
|
|
112
|
+
context = self.snapshot_context(ssid)
|
|
113
|
+
pc = context["pc_score"]
|
|
114
|
+
wifi = context["wifi_score"] if context["wifi_score"] is not None else 5.0
|
|
115
|
+
|
|
116
|
+
risk = (pc + wifi) / 2
|
|
117
|
+
return round(max(0.5, min(5.0, risk)), 2)
|
|
118
|
+
|
|
119
|
+
# ------------------------
|
|
120
|
+
# Wait computation (pure math)
|
|
121
|
+
# ------------------------
|
|
122
|
+
|
|
123
|
+
def compute_wait_wifi(
|
|
124
|
+
self,
|
|
125
|
+
speed: float,
|
|
126
|
+
ssid: str | None = None,
|
|
127
|
+
*,
|
|
128
|
+
context: dict | None = None
|
|
129
|
+
) -> float:
|
|
130
|
+
if context is None:
|
|
131
|
+
context = self.snapshot_context(ssid)
|
|
132
|
+
|
|
133
|
+
pc = context["pc_score"]
|
|
134
|
+
wifi = context["wifi_score"] if context["wifi_score"] is not None else 5.0
|
|
135
|
+
|
|
136
|
+
risk = (pc + wifi) / 2
|
|
137
|
+
return max(0.2, (10 - risk) / speed)
|
|
138
|
+
|
|
139
|
+
def compute_wait_no_wifi(
|
|
140
|
+
self,
|
|
141
|
+
speed: float,
|
|
142
|
+
*,
|
|
143
|
+
context: dict | None = None
|
|
144
|
+
) -> float:
|
|
145
|
+
if context is None:
|
|
146
|
+
context = self.snapshot_context()
|
|
147
|
+
|
|
148
|
+
pc = context["pc_score"]
|
|
149
|
+
return max(0.2, (10 - pc) / speed)
|
|
150
|
+
|
|
151
|
+
# ------------------------
|
|
152
|
+
# Apply Execution Profile
|
|
153
|
+
# ------------------------
|
|
154
|
+
|
|
155
|
+
def apply_profile(self, base_wait: float) -> float:
|
|
156
|
+
"""
|
|
157
|
+
Adjusts wait time according to current execution profile.
|
|
158
|
+
"""
|
|
159
|
+
return base_wait * self.profile.aggressiveness
|
nano_wait/dashboard.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# dashboard.py
|
|
2
|
+
import threading
|
|
3
|
+
import queue
|
|
4
|
+
import tkinter as tk
|
|
5
|
+
from tkinter import ttk
|
|
6
|
+
from time import sleep
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TelemetryDashboard(threading.Thread):
|
|
10
|
+
def __init__(self, telemetry_queue: queue.Queue):
|
|
11
|
+
super().__init__(daemon=True)
|
|
12
|
+
self.q = telemetry_queue
|
|
13
|
+
self.running = True
|
|
14
|
+
|
|
15
|
+
def run(self):
|
|
16
|
+
self.root = tk.Tk()
|
|
17
|
+
self.root.title("NanoWait — Telemetry Dashboard")
|
|
18
|
+
self.root.geometry("420x260")
|
|
19
|
+
self.root.resizable(False, False)
|
|
20
|
+
|
|
21
|
+
self.factor_var = tk.StringVar(value="—")
|
|
22
|
+
self.interval_var = tk.StringVar(value="—")
|
|
23
|
+
self.count_var = tk.StringVar(value="0")
|
|
24
|
+
|
|
25
|
+
ttk.Label(self.root, text="NanoWait Telemetry", font=("Arial", 14, "bold")).pack(pady=10)
|
|
26
|
+
|
|
27
|
+
frame = ttk.Frame(self.root)
|
|
28
|
+
frame.pack(pady=10)
|
|
29
|
+
|
|
30
|
+
ttk.Label(frame, text="Adaptive Factor:").grid(row=0, column=0, sticky="w")
|
|
31
|
+
ttk.Label(frame, textvariable=self.factor_var).grid(row=0, column=1, sticky="e")
|
|
32
|
+
|
|
33
|
+
ttk.Label(frame, text="Interval (s):").grid(row=1, column=0, sticky="w")
|
|
34
|
+
ttk.Label(frame, textvariable=self.interval_var).grid(row=1, column=1, sticky="e")
|
|
35
|
+
|
|
36
|
+
ttk.Label(frame, text="Adjustments:").grid(row=2, column=0, sticky="w")
|
|
37
|
+
ttk.Label(frame, textvariable=self.count_var).grid(row=2, column=1, sticky="e")
|
|
38
|
+
|
|
39
|
+
ttk.Separator(self.root).pack(fill="x", pady=10)
|
|
40
|
+
|
|
41
|
+
self.status = ttk.Label(self.root, text="Running…", foreground="green")
|
|
42
|
+
self.status.pack()
|
|
43
|
+
|
|
44
|
+
self.root.after(100, self.poll_queue)
|
|
45
|
+
self.root.mainloop()
|
|
46
|
+
|
|
47
|
+
def poll_queue(self):
|
|
48
|
+
try:
|
|
49
|
+
while not self.q.empty():
|
|
50
|
+
data = self.q.get_nowait()
|
|
51
|
+
|
|
52
|
+
if data == "__STOP__":
|
|
53
|
+
self.status.config(text="Finished", foreground="gray")
|
|
54
|
+
self.running = False
|
|
55
|
+
self.root.after(800, self.root.destroy)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
self.factor_var.set(str(data["factor"]))
|
|
59
|
+
self.interval_var.set(str(data["interval"]))
|
|
60
|
+
self.count_var.set(str(data["count"]))
|
|
61
|
+
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
if self.running:
|
|
66
|
+
self.root.after(100, self.poll_queue)
|
|
67
|
+
# dashboard.py
|
|
68
|
+
import threading
|
|
69
|
+
import queue
|
|
70
|
+
import tkinter as tk
|
|
71
|
+
from tkinter import ttk
|
|
72
|
+
from time import sleep
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TelemetryDashboard(threading.Thread):
|
|
76
|
+
def __init__(self, telemetry_queue: queue.Queue):
|
|
77
|
+
super().__init__(daemon=True)
|
|
78
|
+
self.q = telemetry_queue
|
|
79
|
+
self.running = True
|
|
80
|
+
|
|
81
|
+
def run(self):
|
|
82
|
+
self.root = tk.Tk()
|
|
83
|
+
self.root.title("NanoWait — Telemetry Dashboard")
|
|
84
|
+
self.root.geometry("420x260")
|
|
85
|
+
self.root.resizable(False, False)
|
|
86
|
+
|
|
87
|
+
self.factor_var = tk.StringVar(value="—")
|
|
88
|
+
self.interval_var = tk.StringVar(value="—")
|
|
89
|
+
self.count_var = tk.StringVar(value="0")
|
|
90
|
+
|
|
91
|
+
ttk.Label(self.root, text="NanoWait Telemetry", font=("Arial", 14, "bold")).pack(pady=10)
|
|
92
|
+
|
|
93
|
+
frame = ttk.Frame(self.root)
|
|
94
|
+
frame.pack(pady=10)
|
|
95
|
+
|
|
96
|
+
ttk.Label(frame, text="Adaptive Factor:").grid(row=0, column=0, sticky="w")
|
|
97
|
+
ttk.Label(frame, textvariable=self.factor_var).grid(row=0, column=1, sticky="e")
|
|
98
|
+
|
|
99
|
+
ttk.Label(frame, text="Interval (s):").grid(row=1, column=0, sticky="w")
|
|
100
|
+
ttk.Label(frame, textvariable=self.interval_var).grid(row=1, column=1, sticky="e")
|
|
101
|
+
|
|
102
|
+
ttk.Label(frame, text="Adjustments:").grid(row=2, column=0, sticky="w")
|
|
103
|
+
ttk.Label(frame, textvariable=self.count_var).grid(row=2, column=1, sticky="e")
|
|
104
|
+
|
|
105
|
+
ttk.Separator(self.root).pack(fill="x", pady=10)
|
|
106
|
+
|
|
107
|
+
self.status = ttk.Label(self.root, text="Running…", foreground="green")
|
|
108
|
+
self.status.pack()
|
|
109
|
+
|
|
110
|
+
self.root.after(100, self.poll_queue)
|
|
111
|
+
self.root.mainloop()
|
|
112
|
+
|
|
113
|
+
def poll_queue(self):
|
|
114
|
+
try:
|
|
115
|
+
while not self.q.empty():
|
|
116
|
+
data = self.q.get_nowait()
|
|
117
|
+
|
|
118
|
+
if data == "__STOP__":
|
|
119
|
+
self.status.config(text="Finished", foreground="gray")
|
|
120
|
+
self.running = False
|
|
121
|
+
self.root.after(800, self.root.destroy)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
self.factor_var.set(str(data["factor"]))
|
|
125
|
+
self.interval_var.set(str(data["interval"]))
|
|
126
|
+
self.count_var.set(str(data["count"]))
|
|
127
|
+
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
if self.running:
|
|
132
|
+
self.root.after(100, self.poll_queue)
|
nano_wait/exceptions.py
ADDED
nano_wait/explain.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from dataclasses import dataclass, asdict
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class ExplainReport:
|
|
8
|
+
requested_time: Optional[float]
|
|
9
|
+
final_time: float
|
|
10
|
+
|
|
11
|
+
speed_input: str | float
|
|
12
|
+
speed_value: float
|
|
13
|
+
smart: bool
|
|
14
|
+
|
|
15
|
+
cpu_score: float
|
|
16
|
+
wifi_score: Optional[float]
|
|
17
|
+
factor: float
|
|
18
|
+
|
|
19
|
+
min_floor_applied: bool
|
|
20
|
+
max_cap_applied: bool
|
|
21
|
+
|
|
22
|
+
timestamp: str
|
|
23
|
+
nano_wait_version: str = "4.1.4"
|
|
24
|
+
|
|
25
|
+
def to_dict(self):
|
|
26
|
+
return asdict(self)
|
|
27
|
+
|
|
28
|
+
def explain(self) -> str:
|
|
29
|
+
lines = [
|
|
30
|
+
f"Requested time: {self.requested_time}s",
|
|
31
|
+
f"Final wait time: {self.final_time}s",
|
|
32
|
+
f"Speed input: {self.speed_input} → {self.speed_value}",
|
|
33
|
+
f"Smart mode: {self.smart}",
|
|
34
|
+
f"CPU score: {self.cpu_score}",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
if self.wifi_score is not None:
|
|
38
|
+
lines.append(f"Wi-Fi score: {self.wifi_score}")
|
|
39
|
+
|
|
40
|
+
lines.extend([
|
|
41
|
+
f"Adaptive factor: {self.factor}",
|
|
42
|
+
f"Minimum floor applied: {self.min_floor_applied}",
|
|
43
|
+
f"Maximum cap applied: {self.max_cap_applied}",
|
|
44
|
+
f"Timestamp: {self.timestamp}",
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
return "\n".join(lines)
|
nano_wait/nano_wait.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# nano_wait.py
|
|
2
|
+
import time
|
|
3
|
+
import queue
|
|
4
|
+
from typing import overload
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from .core import NanoWait, PROFILES
|
|
8
|
+
from .utils import log_message, get_speed_value
|
|
9
|
+
from .exceptions import VisionTimeout
|
|
10
|
+
from .explain import ExplainReport
|
|
11
|
+
from .telemetry import TelemetrySession
|
|
12
|
+
from .dashboard import TelemetryDashboard
|
|
13
|
+
|
|
14
|
+
_ENGINE = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _engine():
|
|
18
|
+
global _ENGINE
|
|
19
|
+
if _ENGINE is None:
|
|
20
|
+
_ENGINE = NanoWait()
|
|
21
|
+
return _ENGINE
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# --------------------------------------
|
|
25
|
+
# Public API
|
|
26
|
+
# --------------------------------------
|
|
27
|
+
|
|
28
|
+
@overload
|
|
29
|
+
def wait(t: float, **kwargs) -> float: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def wait(*, until: str, **kwargs): ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@overload
|
|
37
|
+
def wait(*, icon: str, **kwargs): ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def wait(
|
|
41
|
+
t: float | None = None,
|
|
42
|
+
*,
|
|
43
|
+
until: str | None = None,
|
|
44
|
+
icon: str | None = None,
|
|
45
|
+
region=None,
|
|
46
|
+
timeout: float = 15.0,
|
|
47
|
+
wifi: str | None = None,
|
|
48
|
+
speed: str | float = "normal",
|
|
49
|
+
smart: bool = False,
|
|
50
|
+
verbose: bool = False,
|
|
51
|
+
log: bool = False,
|
|
52
|
+
explain: bool = False,
|
|
53
|
+
telemetry: bool = False,
|
|
54
|
+
profile: str | None = None
|
|
55
|
+
):
|
|
56
|
+
"""
|
|
57
|
+
Adaptive deterministic wait with optional explainable execution,
|
|
58
|
+
execution profiles and local experimental telemetry with live dashboard.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
nw = _engine()
|
|
62
|
+
|
|
63
|
+
# ------------------------
|
|
64
|
+
# Apply execution profile
|
|
65
|
+
# ------------------------
|
|
66
|
+
if profile:
|
|
67
|
+
nw.profile = PROFILES.get(profile, PROFILES["default"])
|
|
68
|
+
|
|
69
|
+
verbose = verbose or nw.profile.verbose
|
|
70
|
+
|
|
71
|
+
# ------------------------
|
|
72
|
+
# Context snapshot
|
|
73
|
+
# ------------------------
|
|
74
|
+
context = nw.snapshot_context(wifi)
|
|
75
|
+
cpu_score = context["pc_score"]
|
|
76
|
+
wifi_score = context["wifi_score"]
|
|
77
|
+
|
|
78
|
+
# ------------------------
|
|
79
|
+
# Telemetry queue + dashboard
|
|
80
|
+
# ------------------------
|
|
81
|
+
telemetry_queue = queue.Queue() if telemetry else None
|
|
82
|
+
|
|
83
|
+
if telemetry:
|
|
84
|
+
TelemetryDashboard(telemetry_queue).start()
|
|
85
|
+
|
|
86
|
+
telemetry_session = TelemetrySession(
|
|
87
|
+
enabled=telemetry,
|
|
88
|
+
cpu_score=cpu_score,
|
|
89
|
+
wifi_score=wifi_score,
|
|
90
|
+
profile=nw.profile.name,
|
|
91
|
+
queue=telemetry_queue
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
telemetry_session.start()
|
|
95
|
+
|
|
96
|
+
# ------------------------
|
|
97
|
+
# Speed resolution
|
|
98
|
+
# ------------------------
|
|
99
|
+
speed_value = nw.smart_speed(wifi) if smart else get_speed_value(speed)
|
|
100
|
+
|
|
101
|
+
# --------------------------------------
|
|
102
|
+
# VISUAL WAIT
|
|
103
|
+
# --------------------------------------
|
|
104
|
+
if until or icon:
|
|
105
|
+
from .vision import VisionMode
|
|
106
|
+
|
|
107
|
+
vision = VisionMode()
|
|
108
|
+
start = time.time()
|
|
109
|
+
|
|
110
|
+
while time.time() - start < timeout:
|
|
111
|
+
|
|
112
|
+
if until:
|
|
113
|
+
state = vision.observe([region] if region else None)
|
|
114
|
+
if state == until:
|
|
115
|
+
telemetry_session.stop()
|
|
116
|
+
return vision.detect_icon("", region)
|
|
117
|
+
|
|
118
|
+
if icon:
|
|
119
|
+
result = vision.detect_icon(icon, region)
|
|
120
|
+
if result.detected:
|
|
121
|
+
telemetry_session.stop()
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
factor = (
|
|
125
|
+
nw.compute_wait_wifi(speed_value, wifi, context=context)
|
|
126
|
+
if wifi
|
|
127
|
+
else nw.compute_wait_no_wifi(speed_value, context=context)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
interval = max(0.05, min(0.5, 1 / factor))
|
|
131
|
+
interval = nw.apply_profile(interval)
|
|
132
|
+
|
|
133
|
+
telemetry_session.record(
|
|
134
|
+
factor=factor,
|
|
135
|
+
interval=interval
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
time.sleep(interval)
|
|
139
|
+
|
|
140
|
+
telemetry_session.stop()
|
|
141
|
+
raise VisionTimeout("Visual condition not detected")
|
|
142
|
+
|
|
143
|
+
# --------------------------------------
|
|
144
|
+
# TIME WAIT
|
|
145
|
+
# --------------------------------------
|
|
146
|
+
factor = (
|
|
147
|
+
nw.compute_wait_wifi(speed_value, wifi, context=context)
|
|
148
|
+
if wifi
|
|
149
|
+
else nw.compute_wait_no_wifi(speed_value, context=context)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
raw_wait = t / factor if t else factor
|
|
153
|
+
wait_time = round(max(0.05, min(raw_wait, t or raw_wait)), 3)
|
|
154
|
+
wait_time = nw.apply_profile(wait_time)
|
|
155
|
+
|
|
156
|
+
min_floor_applied = raw_wait < 0.05
|
|
157
|
+
max_cap_applied = t is not None and raw_wait > t
|
|
158
|
+
|
|
159
|
+
telemetry_session.record(
|
|
160
|
+
factor=factor,
|
|
161
|
+
interval=wait_time
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# ------------------------
|
|
165
|
+
# Verbose / log
|
|
166
|
+
# ------------------------
|
|
167
|
+
if verbose:
|
|
168
|
+
print(
|
|
169
|
+
f"[NanoWait | {nw.profile.name}] "
|
|
170
|
+
f"speed={speed_value:.2f} "
|
|
171
|
+
f"factor={factor:.2f} "
|
|
172
|
+
f"wait={wait_time:.3f}s"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if log:
|
|
176
|
+
log_message(
|
|
177
|
+
f"[NanoWait | {nw.profile.name}] "
|
|
178
|
+
f"speed={speed_value:.2f} "
|
|
179
|
+
f"factor={factor:.2f} "
|
|
180
|
+
f"wait={wait_time:.3f}s"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# ------------------------
|
|
184
|
+
# Explain report
|
|
185
|
+
# ------------------------
|
|
186
|
+
report = None
|
|
187
|
+
if explain:
|
|
188
|
+
report = ExplainReport(
|
|
189
|
+
requested_time=t,
|
|
190
|
+
final_time=wait_time,
|
|
191
|
+
speed_input=speed,
|
|
192
|
+
speed_value=speed_value,
|
|
193
|
+
smart=smart,
|
|
194
|
+
cpu_score=cpu_score,
|
|
195
|
+
wifi_score=wifi_score,
|
|
196
|
+
factor=factor,
|
|
197
|
+
min_floor_applied=min_floor_applied,
|
|
198
|
+
max_cap_applied=max_cap_applied,
|
|
199
|
+
timestamp=datetime.utcnow().isoformat()
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
telemetry_session.stop()
|
|
203
|
+
|
|
204
|
+
# ------------------------
|
|
205
|
+
# Execute wait
|
|
206
|
+
# ------------------------
|
|
207
|
+
time.sleep(wait_time)
|
|
208
|
+
|
|
209
|
+
if explain and telemetry:
|
|
210
|
+
return report, telemetry_session.summary()
|
|
211
|
+
|
|
212
|
+
if explain:
|
|
213
|
+
return report
|
|
214
|
+
|
|
215
|
+
if telemetry:
|
|
216
|
+
return wait_time, telemetry_session.summary()
|
|
217
|
+
|
|
218
|
+
return wait_time
|
nano_wait/telemetry.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# telemetry.py
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import queue
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class TelemetryEvent:
|
|
10
|
+
timestamp: str
|
|
11
|
+
factor: float
|
|
12
|
+
interval: float
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class TelemetrySession:
|
|
17
|
+
enabled: bool = False
|
|
18
|
+
start_time: Optional[float] = None
|
|
19
|
+
end_time: Optional[float] = None
|
|
20
|
+
|
|
21
|
+
cpu_score: Optional[float] = None
|
|
22
|
+
wifi_score: Optional[float] = None
|
|
23
|
+
profile: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
events: List[TelemetryEvent] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
# 👇 NOVO
|
|
28
|
+
queue: Optional[queue.Queue] = None
|
|
29
|
+
|
|
30
|
+
def start(self):
|
|
31
|
+
if self.enabled:
|
|
32
|
+
from time import time
|
|
33
|
+
self.start_time = time()
|
|
34
|
+
|
|
35
|
+
def stop(self):
|
|
36
|
+
if self.enabled:
|
|
37
|
+
from time import time
|
|
38
|
+
self.end_time = time()
|
|
39
|
+
if self.queue:
|
|
40
|
+
self.queue.put("__STOP__")
|
|
41
|
+
|
|
42
|
+
def record(self, *, factor: float, interval: float):
|
|
43
|
+
if not self.enabled:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
event = TelemetryEvent(
|
|
47
|
+
timestamp=datetime.utcnow().isoformat(),
|
|
48
|
+
factor=round(factor, 4),
|
|
49
|
+
interval=round(interval, 4),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.events.append(event)
|
|
53
|
+
|
|
54
|
+
if self.queue:
|
|
55
|
+
self.queue.put({
|
|
56
|
+
"factor": event.factor,
|
|
57
|
+
"interval": event.interval,
|
|
58
|
+
"count": len(self.events)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
def summary(self) -> dict:
|
|
62
|
+
if not self.enabled:
|
|
63
|
+
return {}
|
|
64
|
+
|
|
65
|
+
total_time = (
|
|
66
|
+
round(self.end_time - self.start_time, 4)
|
|
67
|
+
if self.start_time and self.end_time
|
|
68
|
+
else None
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"profile": self.profile,
|
|
73
|
+
"cpu_score": self.cpu_score,
|
|
74
|
+
"wifi_score": self.wifi_score,
|
|
75
|
+
"adjustments": len(self.events),
|
|
76
|
+
"total_time": total_time,
|
|
77
|
+
}
|
nano_wait/utils.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
def get_speed_value(speed):
|
|
4
|
+
"""Converts speed preset or numeric input to float."""
|
|
5
|
+
speed_map = {"slow": 0.8, "normal": 1.5, "fast": 3.0, "ultra": 6.0}
|
|
6
|
+
if isinstance(speed, str):
|
|
7
|
+
return speed_map.get(speed.lower(), 1.5)
|
|
8
|
+
return float(speed)
|
|
9
|
+
|
|
10
|
+
def log_message(text: str):
|
|
11
|
+
"""Saves messages to nano_wait.log."""
|
|
12
|
+
with open("nano_wait.log", "a") as f:
|
|
13
|
+
f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {text}\n")
|
nano_wait/vision.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nano_wait
|
|
3
|
+
Version: 4.0.4
|
|
4
|
+
Summary: Adaptive waiting and execution engine — replaces time.sleep() with system-aware, deterministic waiting.
|
|
5
|
+
Author: Luiz Filipe Seabra de Marco
|
|
6
|
+
Author-email: luizfilipeseabra@icloud.com
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: automation,adaptive wait,smart wait,execution engine,system-aware,deterministic automation,rpa core,testing,performance,psutil,wifi awareness,system context,sleep replacement
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Classifier: Topic :: Software Development :: Testing
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: psutil
|
|
26
|
+
Requires-Dist: pywifi
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
30
|
+
Dynamic: author
|
|
31
|
+
Dynamic: author-email
|
|
32
|
+
Dynamic: classifier
|
|
33
|
+
Dynamic: description
|
|
34
|
+
Dynamic: description-content-type
|
|
35
|
+
Dynamic: keywords
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: provides-extra
|
|
39
|
+
Dynamic: requires-dist
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
Dynamic: summary
|
|
42
|
+
|
|
43
|
+
# NanoWait: The Adaptive Wait Engine for Python
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/nano_wait/)
|
|
46
|
+
[](https://github.com/luizfilipe/NanoWait/blob/main/LICENSE)
|
|
47
|
+
[](https://pypi.org/project/nano_wait/)
|
|
48
|
+
|
|
49
|
+
## 🚀 What is NanoWait?
|
|
50
|
+
|
|
51
|
+
**NanoWait** is a deterministic and adaptive execution wait engine designed to replace Python's standard `time.sleep()`. Instead of waiting for a fixed duration, NanoWait dynamically adjusts the wait time based on **system load (CPU/RAM)** and, optionally, **Wi-Fi signal strength**, ensuring automation scripts remain reliable even in slow or overloaded environments.
|
|
52
|
+
|
|
53
|
+
With the introduction of **Execution Profiles**, NanoWait now offers a semantic layer to manage wait behavior, allowing you to define the operational context clearly and consistently.
|
|
54
|
+
|
|
55
|
+
> **In summary:** you request a base time (e.g., `wait(5)`), and NanoWait ensures a *safe and context-aware wait* that never exceeds the requested time and never falls below a minimum execution floor.
|
|
56
|
+
|
|
57
|
+
### Cross-Platform Stability & Headless Environments
|
|
58
|
+
|
|
59
|
+
NanoWait has undergone significant structural modifications focused on **cross-platform stability**, especially for macOS, and **safe usage in headless environments** (CI, RPA, servers). It explicitly differentiates between graphical and headless modes. In headless environments, no graphical UI is instantiated, preventing crashes like `NSWindow should only be instantiated on the main thread` on macOS with Tkinter issues. This ensures total stability in macOS, CI/CD pipelines, and remote execution.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🛠️ Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install nano_wait
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Optional Module — Vision Mode
|
|
70
|
+
|
|
71
|
+
Visual waiting (icon/state detection) has been intentionally moved to a dedicated package to keep NanoWait lightweight and deterministic.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install nano-wait-vision
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If Vision Mode is not installed, NanoWait will raise a clear runtime error when visual functionalities are requested.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 💡 Quick Guide
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from nano_wait import wait
|
|
85
|
+
import time
|
|
86
|
+
|
|
87
|
+
# Standard sleep
|
|
88
|
+
start = time.time()
|
|
89
|
+
time.sleep(5)
|
|
90
|
+
print(f"time.sleep(): {time.time() - start:.2f}s")
|
|
91
|
+
|
|
92
|
+
# Adaptive wait
|
|
93
|
+
start = time.time()
|
|
94
|
+
wait(5)
|
|
95
|
+
print(f"nano_wait.wait(): {time.time() - start:.2f}s")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
NanoWait **never waits longer than the requested base time** and applies a minimum internal delay of **50 ms** to prevent excessive CPU usage.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ⚙️ Core API
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
wait(
|
|
106
|
+
t: float | None = None,
|
|
107
|
+
*,
|
|
108
|
+
wifi: str | None = None,
|
|
109
|
+
speed: str | float = "normal",
|
|
110
|
+
smart: bool = False,
|
|
111
|
+
explain: bool = False,
|
|
112
|
+
verbose: bool = False,
|
|
113
|
+
log: bool = False,
|
|
114
|
+
profile: str | None = None,
|
|
115
|
+
headless: bool = False # New parameter for explicit headless mode
|
|
116
|
+
) -> float | ExplainReport
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Parameters
|
|
120
|
+
|
|
121
|
+
| Parameter | Description |
|
|
122
|
+
|-----------|---------------------------------------------------------------------------|
|
|
123
|
+
| `t` | Base time in seconds (required for time-based waiting). |
|
|
124
|
+
| `wifi` | Wi-Fi network SSID to assess signal quality (optional). |
|
|
125
|
+
| `speed` | Execution speed preset or numeric value. |
|
|
126
|
+
| `smart` | Activates Smart Context Mode (dynamic speed calculation). |
|
|
127
|
+
| `explain` | Activates Explain Mode, which returns a detailed decision report. |
|
|
128
|
+
| `verbose` | Prints debug information to `stdout`. |
|
|
129
|
+
| `log` | Writes execution data to `nano_wait.log`. |
|
|
130
|
+
| `profile` | Selects a predefined execution profile (e.g., "ci", "rpa"). |
|
|
131
|
+
| `headless`| Explicitly forces headless mode, disabling graphical UI elements. |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🧩 Execution Profiles
|
|
136
|
+
|
|
137
|
+
Execution Profiles introduce a semantic layer over NanoWait's adaptive wait engine. Instead of manually adjusting isolated parameters (speed, aggressiveness, verbosity), you can select an execution profile that represents the operational context in which your code is running — such as continuous integration (CI), automated tests, or robotic process automation (RPA).
|
|
138
|
+
|
|
139
|
+
Each profile encapsulates a coherent set of decisions, ensuring consistency, readability, and reduced cognitive complexity for the user.
|
|
140
|
+
|
|
141
|
+
### 🎯 Why use Execution Profiles?
|
|
142
|
+
|
|
143
|
+
Without profiles, scripts tend to accumulate fragile adjustments:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
wait(2, speed="fast", smart=True, verbose=True)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
With Execution Profiles, the focus shifts to the environment, not mechanical details:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
wait(2, profile="ci")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### ⚙️ How to use
|
|
156
|
+
|
|
157
|
+
Basic usage:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from nano_wait import wait
|
|
161
|
+
|
|
162
|
+
# Executes the wait using the Continuous Integration profile
|
|
163
|
+
wait(2, profile="ci")
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If no profile is specified, NanoWait uses the default profile.
|
|
167
|
+
|
|
168
|
+
### 🧪 Available Profiles
|
|
169
|
+
|
|
170
|
+
| Profile | Recommended Use | General Behavior |
|
|
171
|
+
|-----------|--------------------------------------|-----------------------------------------|
|
|
172
|
+
| `ci` | CI/CD Pipelines | Aggressive waits, verbose enabled |
|
|
173
|
+
| `testing` | Local Automated Tests | Balance between speed and stability |
|
|
174
|
+
| `rpa` | Interface and Human Workflow Automation | More conservative waits |
|
|
175
|
+
| `default` | Generic Execution | Balanced behavior |
|
|
176
|
+
|
|
177
|
+
### 🧠 What does an Execution Profile control?
|
|
178
|
+
|
|
179
|
+
Internally, each profile defines:
|
|
180
|
+
|
|
181
|
+
* Aggressiveness of time adaptation
|
|
182
|
+
* Tolerance to transient instabilities
|
|
183
|
+
* Polling interval
|
|
184
|
+
* Default verbosity (automatic debug)
|
|
185
|
+
|
|
186
|
+
These parameters are applied deterministically to each execution.
|
|
187
|
+
|
|
188
|
+
### 🔄 Integration with Smart Context Mode
|
|
189
|
+
|
|
190
|
+
Execution Profiles do not replace Smart Context Mode — they complement each other.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
wait(
|
|
194
|
+
t=3,
|
|
195
|
+
smart=True,
|
|
196
|
+
profile="testing"
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
In this example:
|
|
201
|
+
|
|
202
|
+
* Smart Mode calculates the optimal speed based on the system
|
|
203
|
+
* The Execution Profile adjusts the overall wait behavior
|
|
204
|
+
|
|
205
|
+
### 🧪 Comparative Example
|
|
206
|
+
|
|
207
|
+
Without Execution Profiles:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
wait(
|
|
211
|
+
t=2,
|
|
212
|
+
speed="fast",
|
|
213
|
+
smart=True,
|
|
214
|
+
verbose=True
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
With Execution Profiles:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
wait(
|
|
222
|
+
t=2,
|
|
223
|
+
profile="ci"
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The second example is more readable, more consistent, and less fragile to future changes.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 🔬 Explain Mode (`explain=True`)
|
|
232
|
+
|
|
233
|
+
Explain Mode makes NanoWait's waiting mechanism deterministic, auditable, and explainable. It does not alter the wait behavior but **reveals how the decision was made**.
|
|
234
|
+
|
|
235
|
+
When activated, `wait()` returns an `ExplainReport` object (instead of a dictionary as previously). This report contains all factors used in the calculation, ideal for debugging, auditing, and benchmarking. The `ExplainReport` includes:
|
|
236
|
+
|
|
237
|
+
* Requested time
|
|
238
|
+
* Final applied time
|
|
239
|
+
* Configured and resolved speed
|
|
240
|
+
* Smart Mode usage
|
|
241
|
+
* CPU score
|
|
242
|
+
* Wi-Fi score
|
|
243
|
+
* Adaptive factor
|
|
244
|
+
* Application of minimum floor or maximum cap
|
|
245
|
+
* Execution timestamp
|
|
246
|
+
|
|
247
|
+
This makes the wait behavior fully auditable and reproducible, providing total transparency for critical environments.
|
|
248
|
+
|
|
249
|
+
### Code Example
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from nano_wait import wait
|
|
253
|
+
|
|
254
|
+
report = wait(
|
|
255
|
+
t=1.5,
|
|
256
|
+
speed="fast",
|
|
257
|
+
smart=True,
|
|
258
|
+
explain=True
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
print(report.explain()) # Use .explain() method for a formatted string output
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Example `ExplainReport` output (simplified):**
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Requested time: 1.5s
|
|
268
|
+
Final wait time: 0.7s
|
|
269
|
+
Speed input: fast -> 0.5
|
|
270
|
+
Smart mode: True
|
|
271
|
+
CPU score: 0.62
|
|
272
|
+
Adaptive factor: 1.39
|
|
273
|
+
Execution profile: default
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 🧠 Smart Context Mode (`smart=True`)
|
|
279
|
+
|
|
280
|
+
When activated, NanoWait automatically calculates the execution speed based on the **average system context score**.
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
wait(10, smart=True, verbose=True)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Example output:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
[NanoWait] speed=3.42 factor=2.05 wait=4.878s
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### How Smart Speed Works
|
|
293
|
+
|
|
294
|
+
* **PC Score** → derived from CPU and memory usage.
|
|
295
|
+
* **Wi-Fi Score** → derived from RSSI (if activated).
|
|
296
|
+
|
|
297
|
+
The final **Smart Speed** is:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
speed = clamp( (pc_score + wifi_score) / 2 , 0.5 , 5.0 )
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
This value is used directly as the execution speed factor.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 🌐 Wi-Fi Awareness
|
|
308
|
+
|
|
309
|
+
If your automation depends on network stability, NanoWait can adapt its waiting behavior based on Wi-Fi signal strength.
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
wait(5, wifi="MyNetwork_5G")
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Supported platforms:
|
|
316
|
+
|
|
317
|
+
* Windows (`pywifi`)
|
|
318
|
+
* macOS (`airport`)
|
|
319
|
+
* Linux (`nmcli`)
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 🖥️ Command Line Interface (CLI)
|
|
324
|
+
|
|
325
|
+
The CLI has been updated to reflect 100% of the API's capabilities, making the tool easy to test, debug, and use in real scripts.
|
|
326
|
+
|
|
327
|
+
**CLI can be executed locally via:**
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
python -m nano_wait.cli 3
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Or as an installed command:**
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
nano-wait 3 --smart --explain
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Supported flags:**
|
|
340
|
+
|
|
341
|
+
* `--smart`
|
|
342
|
+
* `--speed`
|
|
343
|
+
* `--wifi`
|
|
344
|
+
* `--verbose`
|
|
345
|
+
* `--log`
|
|
346
|
+
* `--explain`
|
|
347
|
+
* `--telemetry` (for local telemetry activation)
|
|
348
|
+
* `--profile`
|
|
349
|
+
* `--headless`
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 📊 Local Telemetry (Opt-in, No Remote Collection)
|
|
354
|
+
|
|
355
|
+
NanoWait now includes an experimental, **fully opt-in local telemetry system**. There is **no remote data collection or transmission**.
|
|
356
|
+
|
|
357
|
+
Telemetry records:
|
|
358
|
+
|
|
359
|
+
* `cpu_score`
|
|
360
|
+
* `wifi_score`
|
|
361
|
+
* `adaptive factor`
|
|
362
|
+
* `intervals`
|
|
363
|
+
* Active `profile`
|
|
364
|
+
|
|
365
|
+
In graphical mode (when applicable), a local dashboard might be available. In headless mode, the UI is automatically deactivated. The objective is to allow analysis of wait behavior without compromising security or portability.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## 🛡️ Best Practices & Recommendations
|
|
370
|
+
|
|
371
|
+
1. **Use Profiles:** Prefer `wait(2, profile="testing")` over `wait(2, speed="fast")` for semantic clarity and robustness.
|
|
372
|
+
2. **Smart Mode in Production:** Activate `smart=True` in environments where CPU load is unpredictable to ensure adaptive waiting.
|
|
373
|
+
3. **Audit with Explain:** Use `explain=True` during debugging or intermittent test failures to understand how environmental factors influenced the wait duration.
|
|
374
|
+
4. **Explicit Headless:** When running in Docker, CI, or on servers without a display, explicitly use the `--headless` flag in the CLI or the `headless=True` parameter in the API to prevent unexpected UI attempts.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 📄 License
|
|
379
|
+
|
|
380
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
nano_wait/__init__.py,sha256=SFhmXcZZGJqce153hEDQDaDekRLFs7yxsrDCMq7N6bM,87
|
|
2
|
+
nano_wait/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
|
3
|
+
nano_wait/cli.py,sha256=hY1oCu_T5Yjg0d2HuaxOPzQZRddf03oRlLb3RJWX-40,3283
|
|
4
|
+
nano_wait/core.py,sha256=KIVNW-REpmKNPniHW8o85tw-_vTyvn_uvZxxcesmmc8,5362
|
|
5
|
+
nano_wait/dashboard.py,sha256=VkDzsSSoc9h2q2W7GlVDCxzTVxQPZzaYYKNiO1b9qRI,4478
|
|
6
|
+
nano_wait/exceptions.py,sha256=wgIXzSVkC_o8EvwuhA6ORP2pMzb8jRsSYr7z4J6_gHg,109
|
|
7
|
+
nano_wait/explain.py,sha256=Bn9tokNMq2L6gk7wfgTS8cbSjK4nsgjz1gFYAv28I1k,1216
|
|
8
|
+
nano_wait/nano_wait.py,sha256=jflBsNwtzmceWQTHeevfW1U3zOd4Ew7ijIlx6unNFeY,5588
|
|
9
|
+
nano_wait/telemetry.py,sha256=cka6S3a4reNAL9WctWEuI0zmKxIu8GS-88uiF5yg0pQ,1896
|
|
10
|
+
nano_wait/utils.py,sha256=28qjlES2Yw69mvaj52mmnRqWjtpxu3aG1GwG29dvBYw,486
|
|
11
|
+
nano_wait/vision.py,sha256=NKzJAZFV3152EQBeeygpub8B0VTHo0L9Wt85CsgVa9k,127
|
|
12
|
+
nano_wait-4.0.4.dist-info/licenses/LICENSE,sha256=iu3NnVehM00ZoIpIVkl44-9mCgXVh9iGYMZYXtMv0Mc,1084
|
|
13
|
+
nano_wait-4.0.4.dist-info/METADATA,sha256=0BVeCFSy8jyXKNdMnrd-EbBJF2YxLWcN3x87gJEaFSw,12048
|
|
14
|
+
nano_wait-4.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
nano_wait-4.0.4.dist-info/entry_points.txt,sha256=IISprKfo-qF43ML7ywqV22rUrTm0Gi2y_DpAN03kZhA,49
|
|
16
|
+
nano_wait-4.0.4.dist-info/top_level.txt,sha256=dSiF3Wc3ZEB1pzcp_ubHZeb0OE7n1nPMntkL48uz6aI,10
|
|
17
|
+
nano_wait-4.0.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Luiz Filipe Seabra de marco
|
|
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 @@
|
|
|
1
|
+
nano_wait
|