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 ADDED
@@ -0,0 +1,4 @@
1
+ from .nano_wait import wait
2
+ from .core import NanoWait
3
+
4
+ __all__ = ["wait", "NanoWait"]
nano_wait/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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)
@@ -0,0 +1,2 @@
1
+ class VisionTimeout(Exception):
2
+ """Raised when a visual condition is not detected within the timeout."""
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,4 @@
1
+ raise ImportError(
2
+ "Vision features were moved to 'nano-wait-vision'. "
3
+ "Install with: pip install nano-wait-vision"
4
+ )
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/nano_wait.svg)](https://pypi.org/project/nano_wait/)
46
+ [![License](https://img.shields.io/pypi/l/nano_wait.svg)](https://github.com/luizfilipe/NanoWait/blob/main/LICENSE)
47
+ [![Python Version](https://img.shields.io/pypi/pyversions/nano_wait.svg)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nano-wait = nano_wait.cli:main
@@ -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