netwatchpy 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.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: netwatchpy
3
+ Version: 0.1.0
4
+ Summary: A TUI network usage monitor.
5
+ Author-email: Pranav Kishan <ty.pranavkishan7905@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: textual
10
+ Requires-Dist: psutil
11
+ Requires-Dist: desktop-notifier
12
+
13
+ Netwatch TUI (netwatchpy)
14
+
15
+ A TUI (Text-based User Interface) for monitoring network usage in real-time, with support for data limits and desktop notifications.
16
+
17
+ Installation
18
+
19
+ pip install netwatchpy
20
+
21
+
22
+ Usage
23
+
24
+ Once installed, the netwatch command will be available in your terminal.
25
+
26
+ Run the monitor (monitors all interfaces):
27
+
28
+ netwatch
29
+
30
+
31
+ See all available options:
32
+
33
+ netwatch --help
34
+
35
+
36
+ Examples
37
+
38
+ Set a 10GB data limit:
39
+
40
+ netwatch -l "10GB"
41
+
42
+
43
+ Monitor a specific interface and log to a file:
44
+
45
+ netwatch -i "Wi-Fi" --log "my_usage.csv"
46
+
47
+
48
+ Features
49
+
50
+ Real-time dashboard for Upload/Download speeds and totals.
51
+
52
+ Data limit progress bar and desktop notifications for 80% and 100% usage.
53
+
54
+ Interactive log of all activity.
55
+
56
+ Dark Mode (Ctrl+D) and Command Palette (Ctrl+P).
57
+
58
+ Ability to log all traffic to a CSV file (--log).
@@ -0,0 +1,46 @@
1
+ Netwatch TUI (netwatchpy)
2
+
3
+ A TUI (Text-based User Interface) for monitoring network usage in real-time, with support for data limits and desktop notifications.
4
+
5
+ Installation
6
+
7
+ pip install netwatchpy
8
+
9
+
10
+ Usage
11
+
12
+ Once installed, the netwatch command will be available in your terminal.
13
+
14
+ Run the monitor (monitors all interfaces):
15
+
16
+ netwatch
17
+
18
+
19
+ See all available options:
20
+
21
+ netwatch --help
22
+
23
+
24
+ Examples
25
+
26
+ Set a 10GB data limit:
27
+
28
+ netwatch -l "10GB"
29
+
30
+
31
+ Monitor a specific interface and log to a file:
32
+
33
+ netwatch -i "Wi-Fi" --log "my_usage.csv"
34
+
35
+
36
+ Features
37
+
38
+ Real-time dashboard for Upload/Download speeds and totals.
39
+
40
+ Data limit progress bar and desktop notifications for 80% and 100% usage.
41
+
42
+ Interactive log of all activity.
43
+
44
+ Dark Mode (Ctrl+D) and Command Palette (Ctrl+P).
45
+
46
+ Ability to log all traffic to a CSV file (--log).
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "netwatchpy"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "Pranav Kishan", email = "ty.pranavkishan7905@gmail.com" }
10
+ ]
11
+ description = "A TUI network usage monitor."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ license = { text = "MIT" }
15
+
16
+ dependencies = [
17
+ "textual",
18
+ "psutil",
19
+ "desktop-notifier"
20
+ ]
21
+
22
+ [project.scripts]
23
+ netwatch = "netwatch.tui:main"
24
+
25
+ [tool.setuptools]
26
+ package-dir = {"" = "src"}
27
+ packages = ["netwatch"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,381 @@
1
+ import psutil
2
+ import time
3
+ import argparse
4
+ import threading
5
+ import csv
6
+ from datetime import datetime
7
+ from textwrap import dedent
8
+ from textual.app import App, ComposeResult
9
+ from textual.containers import Container, VerticalScroll, Horizontal
10
+ from textual.widgets import Header, Footer, DataTable, ProgressBar, Static
11
+ from textual.reactive import var
12
+ from desktop_notifier import DesktopNotifier
13
+
14
+ def get_size(byte_val):
15
+ """Converts bytes to a human-readable format (KB, MB, GB)."""
16
+ power = 1024
17
+ n = 0
18
+ power_labels = {0: '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'}
19
+ while byte_val >= power and n < len(power_labels) - 1:
20
+ byte_val /= power
21
+ n += 1
22
+ return f"{byte_val:.2f} {power_labels[n]}B"
23
+
24
+
25
+ def parse_limit(size_str):
26
+ """Parses a size string (e.g., '10GB', '500MB') into bytes."""
27
+ if not size_str:
28
+ return None
29
+ size_str = size_str.upper().strip()
30
+ if size_str.endswith('GB'):
31
+ return int(float(size_str[:-2]) * 1024**3)
32
+ elif size_str.endswith('MB'):
33
+ return int(float(size_str[:-2]) * 1024**2)
34
+ elif size_str.endswith('KB'):
35
+ return int(float(size_str[:-2]) * 1024)
36
+ else:
37
+ try:
38
+ return int(float(size_str))
39
+ except ValueError:
40
+ return None
41
+
42
+
43
+
44
+ class NetworkMonitorThread(threading.Thread):
45
+ """A separate thread that monitors network stats."""
46
+
47
+ def __init__(self, app_callback, interface='all', log_file=None):
48
+ super().__init__()
49
+ self.daemon = True
50
+ self.app_callback = app_callback
51
+ self.interface = interface
52
+ self.stop_event = threading.Event()
53
+ self.log_file = log_file
54
+
55
+ if log_file:
56
+ try:
57
+ with open(log_file, "w", newline="", encoding="utf-8") as f:
58
+ writer = csv.writer(f)
59
+ writer.writerow([
60
+ "Timestamp", "Upload Speed (B/s)", "Download Speed (B/s)",
61
+ "Total Upload", "Total Download", "Total Usage"
62
+ ])
63
+ except Exception as e:
64
+ self.app_callback({"error": f"Failed to create log file: {e}"})
65
+ self.log_file = None
66
+
67
+ def stop(self):
68
+ self.stop_event.set()
69
+
70
+ def run(self):
71
+ total_upload = 0
72
+ total_download = 0
73
+
74
+ try:
75
+ last_stats = psutil.net_io_counters(pernic=True)
76
+ if not last_stats:
77
+ self.app_callback({"error": "No network interfaces found."})
78
+ return
79
+ if self.interface != 'all' and self.interface not in last_stats:
80
+ self.app_callback({"error": f"Interface '{self.interface}' not found."})
81
+ return
82
+ except Exception as e:
83
+ self.app_callback({"error": f"Error getting stats: {e}"})
84
+ return
85
+
86
+ while not self.stop_event.is_set():
87
+ try:
88
+ time.sleep(1)
89
+ current_stats = psutil.net_io_counters(pernic=True)
90
+ if not current_stats:
91
+ continue
92
+
93
+ upload_delta, download_delta = 0, 0
94
+ if self.interface == 'all':
95
+ for iface in current_stats:
96
+ if iface in last_stats:
97
+ upload_delta += current_stats[iface].bytes_sent - last_stats[iface].bytes_sent
98
+ download_delta += current_stats[iface].bytes_recv - last_stats[iface].bytes_recv
99
+ else:
100
+ if self.interface in current_stats and self.interface in last_stats:
101
+ upload_delta = current_stats[self.interface].bytes_sent - last_stats[self.interface].bytes_sent
102
+ download_delta = current_stats[self.interface].bytes_recv - last_stats[self.interface].bytes_recv
103
+
104
+ last_stats = current_stats
105
+ upload_delta = max(upload_delta, 0)
106
+ download_delta = max(download_delta, 0)
107
+ total_upload += upload_delta
108
+ total_download += download_delta
109
+
110
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
111
+ total_usage = total_upload + total_download
112
+ data_packet = {
113
+ "upload_speed": upload_delta,
114
+ "download_speed": download_delta,
115
+ "total_upload": total_upload,
116
+ "total_download": total_download,
117
+ "total_usage": total_usage,
118
+ "timestamp": timestamp
119
+ }
120
+
121
+ if self.log_file:
122
+ with open(self.log_file, "a", newline="", encoding="utf-8") as f:
123
+ writer = csv.writer(f)
124
+ writer.writerow([
125
+ timestamp,
126
+ upload_delta,
127
+ download_delta,
128
+ get_size(total_upload),
129
+ get_size(total_download),
130
+ get_size(total_usage)
131
+ ])
132
+
133
+ self.app_callback(data_packet)
134
+ except Exception as e:
135
+ self.app_callback({"error": f"Error in loop: {e}"})
136
+ time.sleep(3)
137
+
138
+
139
+
140
+ class NetMonitorTUI(App):
141
+ """A Textual TUI for the network monitor."""
142
+
143
+ TITLE = "Network Usage Monitor"
144
+ SUB_TITLE = "Press Ctrl+P for commands, Ctrl+Q to quit"
145
+
146
+ BINDINGS = [
147
+ ("ctrl+p", "command_palette", "Command Palette"),
148
+ ("ctrl+d", "toggle_dark", "Toggle Dark Mode"),
149
+ ("r", "reset_counters", "Reset Counters"),
150
+ ("ctrl+q", "quit", "Quit"),
151
+ ]
152
+
153
+ CSS = dedent("""
154
+ Screen {
155
+ background: #f8f8f8;
156
+ color: black;
157
+ }
158
+
159
+ .-dark-mode Screen {
160
+ background: #101010;
161
+ color: #f0f0f0;
162
+ }
163
+
164
+ #main_container {
165
+ layout: vertical;
166
+ }
167
+
168
+ #summary_cards {
169
+ layout: horizontal;
170
+ height: auto;
171
+ padding: 1 0;
172
+ }
173
+
174
+ .summary_card {
175
+ width: 1fr;
176
+ min-height: 5;
177
+ border: solid black;
178
+ padding: 1;
179
+ margin: 0 1;
180
+ background: #e8e8e8;
181
+ }
182
+
183
+ .-dark-mode .summary_card {
184
+ border: solid #888;
185
+ background: #222;
186
+ color: #e0e0e0;
187
+ }
188
+
189
+ #limit_container {
190
+ height: auto;
191
+ padding: 0 1 1 1;
192
+ }
193
+
194
+ #stats_table {
195
+ height: 1fr;
196
+ margin: 0 1;
197
+ border: solid black;
198
+ }
199
+
200
+ .-dark-mode #stats_table {
201
+ border: solid #666;
202
+ color: #e0e0e0;
203
+ }
204
+
205
+ ProgressBar > .progress-bar--bar {
206
+ background: #007acc;
207
+ }
208
+
209
+ .-dark-mode ProgressBar > .progress-bar--bar {
210
+ background: #55aaff;
211
+ }
212
+
213
+ #footer {
214
+ color: white;
215
+ }
216
+
217
+ #header {
218
+ color: white;
219
+ }
220
+
221
+ #error_box {
222
+ height: auto;
223
+ padding: 1 2;
224
+ color: red;
225
+ display: none;
226
+ }
227
+ """)
228
+
229
+ total_usage = var(0)
230
+ total_upload = var(0)
231
+ total_download = var(0)
232
+ upload_speed = var(0)
233
+ download_speed = var(0)
234
+ dark = var(False)
235
+
236
+ def __init__(self, interface='all', limit_str=None, log_file=None):
237
+ super().__init__()
238
+ self.interface = interface
239
+ self.limit_bytes = parse_limit(limit_str)
240
+ self.limit_str = limit_str or "No Limit"
241
+ self.monitor_thread = None
242
+ self.alert_80_sent = False
243
+ self.alert_100_sent = False
244
+ self.notifier = DesktopNotifier(app_name="Netwatch")
245
+ self.log_file = log_file
246
+
247
+ def compose(self) -> ComposeResult:
248
+ yield Header()
249
+ with VerticalScroll(id="main_container"):
250
+ with Horizontal(id="summary_cards"):
251
+ yield Static("Total Download\n[b]0.00 B[/b]", id="total-dl-card", classes="summary_card")
252
+ yield Static("Total Upload\n[b]0.00 B[/b]", id="total-ul-card", classes="summary_card")
253
+ yield Static("Total Usage\n[b]0.00 B[/b]", id="total-usage-card", classes="summary_card")
254
+
255
+ with Container(id="limit_container"):
256
+ if self.limit_bytes:
257
+ yield Static(f"Usage Limit: {get_size(self.limit_bytes)}")
258
+ yield ProgressBar(id="limit_bar", total=self.limit_bytes, show_eta=False)
259
+ else:
260
+ yield Static("Usage Limit: Not Set")
261
+
262
+ yield Static(id="error_box")
263
+ yield DataTable(id="stats_table")
264
+
265
+ yield Footer()
266
+
267
+ def on_mount(self) -> None:
268
+ table = self.query_one(DataTable)
269
+ table.add_column("Time", key="time")
270
+ table.add_column("Up Speed", key="up_spd")
271
+ table.add_column("Down Speed", key="dl_spd")
272
+ table.add_column("Total Up", key="total_up")
273
+ table.add_column("Total Down", key="total_dl")
274
+ table.add_column("Total Usage", key="total")
275
+
276
+ self.monitor_thread = NetworkMonitorThread(
277
+ app_callback=self.on_data_update,
278
+ interface=self.interface,
279
+ log_file=self.log_file
280
+ )
281
+ self.monitor_thread.start()
282
+
283
+ def on_exit(self) -> None:
284
+ if self.monitor_thread:
285
+ self.monitor_thread.stop()
286
+
287
+ def on_data_update(self, data: dict) -> None:
288
+ self.call_from_thread(self._process_data_packet, data)
289
+
290
+ def _process_data_packet(self, data: dict) -> None:
291
+ if "error" in data:
292
+ error_box = self.query_one("#error_box")
293
+ error_box.update(f"ERROR: {data['error']}")
294
+ error_box.styles.display = "block"
295
+ return
296
+
297
+ self.upload_speed = data["upload_speed"]
298
+ self.download_speed = data["download_speed"]
299
+ self.total_upload = data["total_upload"]
300
+ self.total_download = data["total_download"]
301
+ self.total_usage = data["total_usage"]
302
+
303
+ table = self.query_one(DataTable)
304
+ table.add_row(
305
+ data["timestamp"].split(" ")[1],
306
+ f"{get_size(self.upload_speed)}/s",
307
+ f"{get_size(self.download_speed)}/s",
308
+ get_size(self.total_upload),
309
+ get_size(self.total_download),
310
+ get_size(self.total_usage)
311
+ )
312
+ table.scroll_end(animate=False)
313
+
314
+ if table.row_count > 50:
315
+ first_key = next(iter(table.rows.keys()))
316
+ table.remove_row(first_key)
317
+
318
+ def action_toggle_dark(self):
319
+ """Toggle dark/light mode properly."""
320
+ self.dark = not self.dark
321
+ self.set_class(self.dark, "-dark-mode")
322
+ self.sub_title = "🌙 Dark Mode ON" if self.dark else "☀️ Light Mode ON"
323
+
324
+ def action_reset_counters(self):
325
+ """Reset counters."""
326
+ self.total_upload = self.total_download = self.total_usage = 0
327
+ self.alert_80_sent = self.alert_100_sent = False
328
+ self.sub_title = "Counters Reset!"
329
+ if self.limit_bytes:
330
+ bar = self.query_one(ProgressBar)
331
+ bar.styles.color = None
332
+
333
+ def watch_total_download(self, new_val: int) -> None:
334
+ self.query_one("#total-dl-card").update(f"Total Download\n[b]{get_size(new_val)}[/b]")
335
+
336
+ def watch_total_upload(self, new_val: int) -> None:
337
+ self.query_one("#total-ul-card").update(f"Total Upload\n[b]{get_size(new_val)}[/b]")
338
+
339
+ async def watch_total_usage(self, new_total_usage: int) -> None:
340
+ self.query_one("#total-usage-card").update(f"Total Usage\n[b]{get_size(new_total_usage)}[/b]")
341
+ if self.limit_bytes:
342
+ bar = self.query_one(ProgressBar)
343
+ bar.progress = new_total_usage
344
+ if new_total_usage >= 0.8 * self.limit_bytes and not self.alert_80_sent:
345
+ self.alert_80_sent = True
346
+ bar.styles.color = "yellow"
347
+ self.sub_title = "⚠️ 80% of limit reached!"
348
+ try:
349
+ await self.notifier.send(
350
+ title="Netwatch: 80% Usage Warning",
351
+ message=f"You have used {get_size(new_total_usage)} of your {get_size(self.limit_bytes)} limit."
352
+ )
353
+ except Exception as e:
354
+ print(f"[Notification Error] {e}")
355
+ if new_total_usage >= self.limit_bytes and not self.alert_100_sent:
356
+ self.alert_100_sent = True
357
+ bar.styles.color = "red"
358
+ self.sub_title = "🚨 Data limit exceeded!"
359
+ try:
360
+ await self.notifier.send(
361
+ title="Netwatch: Data Limit Exceeded!",
362
+ message=f"You have exceeded your {get_size(self.limit_bytes)} data limit."
363
+ )
364
+ except Exception as e:
365
+ print(f"[Notification Error] {e}")
366
+
367
+
368
+
369
+ def main():
370
+ parser = argparse.ArgumentParser(description="Network Usage Monitor TUI")
371
+ parser.add_argument('-i', '--interface', type=str, default='all', help="Network interface to monitor (e.g., 'Wi-Fi'). Default is 'all'.")
372
+ parser.add_argument('-l', '--limit', type=str, help="Set data usage cap (e.g., '10GB', '500MB').")
373
+ parser.add_argument('--log', type=str, help="Optional CSV file to log network usage data.")
374
+ args = parser.parse_args()
375
+
376
+ app = NetMonitorTUI(interface=args.interface, limit_str=args.limit, log_file=args.log)
377
+ app.run()
378
+
379
+
380
+ if __name__ == "__main__":
381
+ main()
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: netwatchpy
3
+ Version: 0.1.0
4
+ Summary: A TUI network usage monitor.
5
+ Author-email: Pranav Kishan <ty.pranavkishan7905@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: textual
10
+ Requires-Dist: psutil
11
+ Requires-Dist: desktop-notifier
12
+
13
+ Netwatch TUI (netwatchpy)
14
+
15
+ A TUI (Text-based User Interface) for monitoring network usage in real-time, with support for data limits and desktop notifications.
16
+
17
+ Installation
18
+
19
+ pip install netwatchpy
20
+
21
+
22
+ Usage
23
+
24
+ Once installed, the netwatch command will be available in your terminal.
25
+
26
+ Run the monitor (monitors all interfaces):
27
+
28
+ netwatch
29
+
30
+
31
+ See all available options:
32
+
33
+ netwatch --help
34
+
35
+
36
+ Examples
37
+
38
+ Set a 10GB data limit:
39
+
40
+ netwatch -l "10GB"
41
+
42
+
43
+ Monitor a specific interface and log to a file:
44
+
45
+ netwatch -i "Wi-Fi" --log "my_usage.csv"
46
+
47
+
48
+ Features
49
+
50
+ Real-time dashboard for Upload/Download speeds and totals.
51
+
52
+ Data limit progress bar and desktop notifications for 80% and 100% usage.
53
+
54
+ Interactive log of all activity.
55
+
56
+ Dark Mode (Ctrl+D) and Command Palette (Ctrl+P).
57
+
58
+ Ability to log all traffic to a CSV file (--log).
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/netwatch/__init__.py
4
+ src/netwatch/tui.py
5
+ src/netwatchpy.egg-info/PKG-INFO
6
+ src/netwatchpy.egg-info/SOURCES.txt
7
+ src/netwatchpy.egg-info/dependency_links.txt
8
+ src/netwatchpy.egg-info/entry_points.txt
9
+ src/netwatchpy.egg-info/requires.txt
10
+ src/netwatchpy.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ netwatch = netwatch.tui:main
@@ -0,0 +1,3 @@
1
+ textual
2
+ psutil
3
+ desktop-notifier
@@ -0,0 +1 @@
1
+ netwatch