proker 2.0.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,7 @@
1
+ include pyproject.toml
2
+ include README.md
3
+ recursive-include proteomicsviewer/server/templates *
4
+ recursive-exclude Test\ data *
5
+ exclude proker_logo.jpg
6
+ exclude *.bat
7
+ exclude *.command
proker-2.0.0/PKG-INFO ADDED
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: proker
3
+ Version: 2.0.0
4
+ Summary: Pro-ker Proteomics Viewer — an interactive browser-based proteomics data visualization tool
5
+ Author-email: Billy Ngo <billy.ngo0108@gmail.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://github.com/billy-ngo/proteomics-viewer
8
+ Project-URL: Repository, https://github.com/billy-ngo/proteomics-viewer
9
+ Keywords: proteomics,mass-spectrometry,visualization,MaxQuant,bioinformatics,pro-ker
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: fastapi>=0.111.0
22
+ Requires-Dist: uvicorn[standard]>=0.29.0
23
+ Requires-Dist: python-multipart>=0.0.9
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: proker
3
+ Version: 2.0.0
4
+ Summary: Pro-ker Proteomics Viewer — an interactive browser-based proteomics data visualization tool
5
+ Author-email: Billy Ngo <billy.ngo0108@gmail.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://github.com/billy-ngo/proteomics-viewer
8
+ Project-URL: Repository, https://github.com/billy-ngo/proteomics-viewer
9
+ Keywords: proteomics,mass-spectrometry,visualization,MaxQuant,bioinformatics,pro-ker
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: fastapi>=0.111.0
22
+ Requires-Dist: uvicorn[standard]>=0.29.0
23
+ Requires-Dist: python-multipart>=0.0.9
@@ -0,0 +1,19 @@
1
+ MANIFEST.in
2
+ pyproject.toml
3
+ proker.egg-info/PKG-INFO
4
+ proker.egg-info/SOURCES.txt
5
+ proker.egg-info/dependency_links.txt
6
+ proker.egg-info/entry_points.txt
7
+ proker.egg-info/requires.txt
8
+ proker.egg-info/top_level.txt
9
+ proteomicsviewer/__init__.py
10
+ proteomicsviewer/__main__.py
11
+ proteomicsviewer/cli.py
12
+ proteomicsviewer/icon.py
13
+ proteomicsviewer/install_shortcut.py
14
+ proteomicsviewer/server/__init__.py
15
+ proteomicsviewer/server/main.py
16
+ proteomicsviewer/server/parser.py
17
+ proteomicsviewer/server/state.py
18
+ proteomicsviewer/server/templates/index.html
19
+ proteomicsviewer/server/templates/logo.svg
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ proker = proteomicsviewer.cli:main
@@ -0,0 +1,3 @@
1
+ fastapi>=0.111.0
2
+ uvicorn[standard]>=0.29.0
3
+ python-multipart>=0.0.9
@@ -0,0 +1 @@
1
+ proteomicsviewer
@@ -0,0 +1,3 @@
1
+ """Pro-ker Proteomics Viewer — an interactive browser-based proteomics data visualization tool."""
2
+
3
+ __version__ = "2.0.0"
@@ -0,0 +1,7 @@
1
+ """Entry point for `python -m proteomicsviewer` and the `protview` CLI command."""
2
+
3
+ import sys
4
+ from proteomicsviewer.cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
@@ -0,0 +1,507 @@
1
+ """
2
+ Pro-ker Proteomics Analysis CLI — launch the viewer from the command line.
3
+
4
+ Usage:
5
+ proker # start on default port 8050
6
+ proker data.txt # start and auto-load a file
7
+ proker --port 9000 # start on a custom port
8
+ proker --no-browser # start without opening the browser
9
+ proker --install # create a desktop shortcut
10
+ proker --update # check for updates and install if available
11
+ proker --no-update # skip the automatic update check
12
+ """
13
+
14
+ import argparse
15
+ import atexit
16
+ import json
17
+ import os
18
+ import subprocess
19
+ import sys
20
+ import threading
21
+ import time
22
+ import webbrowser
23
+ from pathlib import Path
24
+
25
+ _CONFIG_DIR = Path.home() / ".proker"
26
+ _LOCK_FILE = _CONFIG_DIR / "server.lock"
27
+ _FIRST_RUN_MARKER = _CONFIG_DIR / ".shortcut_prompted"
28
+ _UPDATE_CHECK_FILE = _CONFIG_DIR / ".last_update_check"
29
+ _VERSION_MARKER = _CONFIG_DIR / ".installed_version"
30
+
31
+ _UPDATE_CHECK_INTERVAL = 3600 # 1 hour
32
+
33
+
34
+ # ── Welcome message ──────────────────────────────────────────────
35
+ def _show_welcome_if_new():
36
+ ver = _get_installed_version()
37
+ try:
38
+ prev = _VERSION_MARKER.read_text().strip() if _VERSION_MARKER.exists() else None
39
+ except Exception:
40
+ prev = None
41
+
42
+ if prev == ver:
43
+ return
44
+
45
+ try:
46
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
47
+ _VERSION_MARKER.write_text(ver)
48
+ except Exception:
49
+ pass
50
+
51
+ try:
52
+ if sys.stdout is None:
53
+ return
54
+ sys.stdout.write('')
55
+ except Exception:
56
+ return
57
+
58
+ is_upgrade = prev is not None
59
+ print()
60
+ print(f" {'=' * 44}")
61
+ if is_upgrade:
62
+ print(f" Pro-ker Proteomics Analysis updated to v{ver}")
63
+ else:
64
+ print(f" Pro-ker Proteomics Analysis v{ver} installed")
65
+ print(f" {'=' * 44}")
66
+ print()
67
+ if not is_upgrade:
68
+ print(" Supported formats:")
69
+ print(" MaxQuant proteinGroups.txt (tab-separated)")
70
+ print()
71
+ print(" Quick start:")
72
+ print(" - Upload a proteinGroups.txt file")
73
+ print(" - Assign samples to groups")
74
+ print(" - Configure processing options")
75
+ print(" - Add visualizations to the canvas")
76
+ print()
77
+ print(" Commands:")
78
+ print(" proker Launch the viewer")
79
+ print(" proker file.txt Load a file on start")
80
+ print(" proker --update Check for updates")
81
+ print(" proker --install Create a desktop shortcut")
82
+ print(" proker --version Show installed version")
83
+ print()
84
+
85
+
86
+ # ── Auto-update ──────────────────────────────────────────────────
87
+ def _get_installed_version():
88
+ try:
89
+ from proteomicsviewer import __version__
90
+ return __version__
91
+ except Exception:
92
+ return "0.0.0"
93
+
94
+
95
+ def _get_pypi_version():
96
+ try:
97
+ import urllib.request
98
+ url = "https://pypi.org/pypi/proker/json"
99
+ with urllib.request.urlopen(url, timeout=3) as resp:
100
+ data = json.loads(resp.read())
101
+ return data["info"]["version"]
102
+ except Exception:
103
+ return None
104
+
105
+
106
+ def _version_tuple(v):
107
+ try:
108
+ return tuple(int(x) for x in v.split(".")[:3])
109
+ except Exception:
110
+ return (0, 0, 0)
111
+
112
+
113
+ def _should_check_update():
114
+ try:
115
+ if _UPDATE_CHECK_FILE.exists():
116
+ last = float(_UPDATE_CHECK_FILE.read_text().strip())
117
+ return (time.time() - last) > _UPDATE_CHECK_INTERVAL
118
+ except Exception:
119
+ pass
120
+ return True
121
+
122
+
123
+ def _record_update_check():
124
+ try:
125
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
126
+ _UPDATE_CHECK_FILE.write_text(str(time.time()))
127
+ except Exception:
128
+ pass
129
+
130
+
131
+ def _do_upgrade():
132
+ cmds = [
133
+ [sys.executable, "-m", "pip", "install", "--upgrade", "proker"],
134
+ [sys.executable, "-m", "pip", "install", "--upgrade", "--user", "proker"],
135
+ ]
136
+ last_err = None
137
+ for cmd in cmds:
138
+ try:
139
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
140
+ if result.returncode == 0:
141
+ return True
142
+ last_err = result.stderr.strip()[:200]
143
+ except Exception as e:
144
+ last_err = str(e)
145
+ if last_err:
146
+ _log(f" pip failed: {last_err}")
147
+ return False
148
+
149
+
150
+ def _log(msg):
151
+ try:
152
+ print(msg)
153
+ except Exception:
154
+ pass
155
+ try:
156
+ log = _CONFIG_DIR / "update.log"
157
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
158
+ with open(log, "a") as f:
159
+ f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {msg}\n")
160
+ except Exception:
161
+ pass
162
+
163
+
164
+ def check_and_update(force=False):
165
+ if not force and not _should_check_update():
166
+ return ('skip', None)
167
+
168
+ _record_update_check()
169
+ installed = _get_installed_version()
170
+ latest = _get_pypi_version()
171
+
172
+ if latest is None:
173
+ return ('skip', None)
174
+ if _version_tuple(latest) <= _version_tuple(installed):
175
+ return ('current', None)
176
+
177
+ _log(f" Updating Pro-ker: {installed} -> {latest} ...")
178
+ if _do_upgrade():
179
+ _log(f" Updated to {latest}.")
180
+ return ('updated', latest)
181
+ else:
182
+ _log(f" Update failed (retry with: proker --update)")
183
+ return ('failed', latest)
184
+
185
+
186
+ def _check_update_with_prompt():
187
+ try:
188
+ _record_update_check()
189
+ installed = _get_installed_version()
190
+ latest = _get_pypi_version()
191
+
192
+ if latest is None or _version_tuple(latest) <= _version_tuple(installed):
193
+ return
194
+
195
+ has_terminal = False
196
+ try:
197
+ if sys.stdout is not None and hasattr(sys.stdout, 'write'):
198
+ sys.stdout.write('')
199
+ has_terminal = True
200
+ except Exception:
201
+ pass
202
+
203
+ should_update = False
204
+
205
+ if has_terminal:
206
+ try:
207
+ print(f"\n Update available: {installed} -> {latest}")
208
+ answer = input(" Install update now? [Y/n]: ").strip()
209
+ should_update = answer.lower() != 'n'
210
+ except (EOFError, OSError):
211
+ should_update = _tk_update_prompt(installed, latest)
212
+ else:
213
+ should_update = _tk_update_prompt(installed, latest)
214
+
215
+ if should_update:
216
+ _log(f" Updating Pro-ker: {installed} -> {latest} ...")
217
+ if _do_upgrade():
218
+ _log(f" Updated to {latest}.")
219
+ if has_terminal:
220
+ print(f"\n Please run 'proker' again to launch the new version.\n")
221
+ sys.exit(0)
222
+ else:
223
+ try:
224
+ import tkinter as tk
225
+ from tkinter import messagebox
226
+ root = tk.Tk(); root.withdraw()
227
+ messagebox.showinfo(
228
+ "Pro-ker",
229
+ f"Updated to v{latest}.\n\nThe viewer will now relaunch."
230
+ )
231
+ root.destroy()
232
+ except Exception:
233
+ pass
234
+ subprocess.Popen([sys.executable, "-m", "proteomicsviewer", "--no-update"])
235
+ sys.exit(0)
236
+ else:
237
+ _log(f" Update failed. Retry with: proker --update")
238
+ except Exception:
239
+ pass
240
+
241
+
242
+ def _tk_update_prompt(installed, latest):
243
+ try:
244
+ import tkinter as tk
245
+ from tkinter import messagebox
246
+ root = tk.Tk(); root.withdraw()
247
+ answer = messagebox.askyesno(
248
+ "Pro-ker Update",
249
+ f"A new version is available: {installed} -> {latest}\n\n"
250
+ f"Install the update now?",
251
+ )
252
+ root.destroy()
253
+ return answer
254
+ except Exception:
255
+ return False
256
+
257
+
258
+ # ── Instance detection ───────────────────────────────────────────
259
+ def _pid_alive(pid):
260
+ if sys.platform == "win32":
261
+ import ctypes
262
+ kernel32 = ctypes.windll.kernel32
263
+ handle = kernel32.OpenProcess(0x100000, False, pid)
264
+ if handle:
265
+ kernel32.CloseHandle(handle)
266
+ return True
267
+ return False
268
+ try:
269
+ os.kill(pid, 0)
270
+ return True
271
+ except (OSError, ProcessLookupError, PermissionError):
272
+ return False
273
+
274
+
275
+ def _http_check(host, port, timeout=0.5):
276
+ try:
277
+ import urllib.request
278
+ url = f"http://{'localhost' if host in ('0.0.0.0', '127.0.0.1') else host}:{port}/health"
279
+ with urllib.request.urlopen(url, timeout=timeout) as resp:
280
+ return json.loads(resp.read()).get("status") == "ok"
281
+ except Exception:
282
+ return False
283
+
284
+
285
+ def _check_existing_server(host, port):
286
+ try:
287
+ if _LOCK_FILE.exists():
288
+ data = json.loads(_LOCK_FILE.read_text())
289
+ lock_host = data.get("host", "127.0.0.1")
290
+ lock_port = data.get("port", 8050)
291
+ lock_pid = data.get("pid")
292
+ if lock_pid and _pid_alive(lock_pid) and _http_check(lock_host, lock_port):
293
+ h = "localhost" if lock_host in ("0.0.0.0", "127.0.0.1") else lock_host
294
+ return f"http://{h}:{lock_port}"
295
+ _LOCK_FILE.unlink(missing_ok=True)
296
+ except Exception:
297
+ pass
298
+
299
+ if _http_check(host, port):
300
+ h = "localhost" if host in ("0.0.0.0", "127.0.0.1") else host
301
+ return f"http://{h}:{port}"
302
+ return None
303
+
304
+
305
+ # ── Lock file helpers ────────────────────────────────────────────
306
+ def _write_lock(host, port):
307
+ try:
308
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
309
+ _LOCK_FILE.write_text(json.dumps({"host": host, "port": port, "pid": os.getpid()}))
310
+ except Exception:
311
+ pass
312
+
313
+
314
+ def _remove_lock():
315
+ try:
316
+ if _LOCK_FILE.exists():
317
+ data = json.loads(_LOCK_FILE.read_text())
318
+ if data.get("pid") == os.getpid():
319
+ _LOCK_FILE.unlink()
320
+ except Exception:
321
+ pass
322
+
323
+
324
+ # ── First-run shortcut prompt ────────────────────────────────────
325
+ def _offer_shortcut_install():
326
+ if _FIRST_RUN_MARKER.exists():
327
+ return
328
+ try:
329
+ _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
330
+ _FIRST_RUN_MARKER.write_text("prompted")
331
+ except Exception:
332
+ return
333
+
334
+ try:
335
+ answer = input(" Create a desktop shortcut? [Y/n]: ").strip()
336
+ if answer.lower() != 'n':
337
+ try:
338
+ from proteomicsviewer.install_shortcut import main as install_main
339
+ install_main()
340
+ except ImportError:
341
+ print(" Shortcut creation requires tkinter.")
342
+ print(" You can create a shortcut later with: proker --install")
343
+ except Exception as e:
344
+ print(f" Shortcut creation failed: {e}")
345
+ print(" You can try again later with: proker --install")
346
+ return
347
+ except (EOFError, OSError):
348
+ pass
349
+
350
+ try:
351
+ import tkinter as tk
352
+ from tkinter import messagebox
353
+ root = tk.Tk(); root.withdraw()
354
+ answer = messagebox.askyesno(
355
+ "Pro-ker",
356
+ "Would you like to create a desktop shortcut?\n\n"
357
+ "You can also do this later with: proker --install",
358
+ )
359
+ root.destroy()
360
+ if answer:
361
+ from proteomicsviewer.install_shortcut import main as install_main
362
+ install_main()
363
+ except Exception:
364
+ pass
365
+
366
+
367
+ # ── Browser opener ───────────────────────────────────────────────
368
+ def _open_when_ready(url, timeout=10):
369
+ import urllib.request
370
+ deadline = time.monotonic() + timeout
371
+ while time.monotonic() < deadline:
372
+ try:
373
+ urllib.request.urlopen(f"{url}/health", timeout=0.5)
374
+ break
375
+ except Exception:
376
+ time.sleep(0.2)
377
+ webbrowser.open(url)
378
+
379
+
380
+ # ── Entry point ──────────────────────────────────────────────────
381
+ def main():
382
+ parser = argparse.ArgumentParser(
383
+ prog="proker",
384
+ description="Pro-ker Proteomics Analysis — interactive proteomics data visualization",
385
+ )
386
+ parser.add_argument("file", nargs="?", default=None,
387
+ help="Path to a proteinGroups.txt file to auto-load")
388
+ parser.add_argument("--port", type=int, default=8050, help="Port (default: 8050)")
389
+ parser.add_argument("--host", type=str, default="127.0.0.1", help="Host (default: 127.0.0.1)")
390
+ parser.add_argument("--no-browser", action="store_true", help="Don't open the browser")
391
+ parser.add_argument("--install", action="store_true", help="Create a desktop shortcut")
392
+ parser.add_argument("--update", action="store_true", help="Check for updates now")
393
+ parser.add_argument("--no-update", action="store_true", help="Skip automatic update check")
394
+ parser.add_argument("--version", action="store_true", help="Show version and exit")
395
+ args = parser.parse_args()
396
+
397
+ if args.version:
398
+ print(f"Pro-ker Proteomics Analysis {_get_installed_version()}")
399
+ return 0
400
+
401
+ if args.install:
402
+ try:
403
+ from proteomicsviewer.install_shortcut import main as install_main
404
+ install_main()
405
+ except ImportError:
406
+ print(" Shortcut creation requires tkinter.")
407
+ print(" Install it with: conda install tk (or) sudo apt install python3-tk")
408
+ except Exception as e:
409
+ print(f" Shortcut creation failed: {e}")
410
+ return 0
411
+
412
+ # Manual update command
413
+ if args.update:
414
+ status, ver = check_and_update(force=True)
415
+ if status == 'updated':
416
+ print(f" Pro-ker updated to {ver}.")
417
+ print(f" Run 'proker' to launch the new version.")
418
+ elif status == 'failed':
419
+ print(f" Update to {ver} failed.")
420
+ print(f" Check ~/.proker/update.log for details.")
421
+ print(f" Or update manually: pip install --upgrade proker")
422
+ elif status == 'skip':
423
+ print(f" Could not reach PyPI. Check your internet connection.")
424
+ else:
425
+ print(f" Pro-ker {_get_installed_version()} is up to date.")
426
+ return 0
427
+
428
+ # Welcome message on first run / upgrade
429
+ _show_welcome_if_new()
430
+
431
+ # Pre-launch update check
432
+ has_terminal = False
433
+ try:
434
+ if sys.stdout is not None and hasattr(sys.stdout, 'write'):
435
+ sys.stdout.write('')
436
+ has_terminal = True
437
+ except Exception:
438
+ pass
439
+
440
+ if not args.no_update and has_terminal:
441
+ _check_update_with_prompt()
442
+
443
+ # Single-instance check
444
+ existing = _check_existing_server(args.host, args.port)
445
+ if existing:
446
+ if not args.no_browser:
447
+ webbrowser.open(existing)
448
+ return 0
449
+
450
+ # First launch — offer desktop shortcut
451
+ _offer_shortcut_install()
452
+
453
+ # Store file path for auto-load
454
+ if args.file:
455
+ filepath = Path(args.file).resolve()
456
+ if not filepath.exists():
457
+ print(f"Error: file not found: {filepath}", file=sys.stderr)
458
+ return 1
459
+ os.environ["PROTVIEW_AUTOLOAD"] = str(filepath)
460
+
461
+ url = f"http://{'localhost' if args.host in ('0.0.0.0',) else args.host}:{args.port}"
462
+
463
+ if not args.no_browser:
464
+ threading.Thread(target=_open_when_ready, args=(url,), daemon=True).start()
465
+
466
+ # Heavy imports
467
+ backend_dir = os.path.join(os.path.dirname(__file__), "server")
468
+ sys.path.insert(0, backend_dir)
469
+ os.environ["PROTVIEW_AUTO_SHUTDOWN"] = "1"
470
+
471
+ from proteomicsviewer.server.main import app # noqa: E402
472
+
473
+ _write_lock(args.host, args.port)
474
+ atexit.register(_remove_lock)
475
+
476
+ # Background update check for pythonw launches
477
+ if not args.no_update and not has_terminal:
478
+ def _bg_update_check():
479
+ time.sleep(3)
480
+ try:
481
+ installed = _get_installed_version()
482
+ latest = _get_pypi_version()
483
+ if latest and _version_tuple(latest) > _version_tuple(installed):
484
+ _log(f" Update available: {installed} -> {latest}. Run 'proker --update'.")
485
+ if _tk_update_prompt(installed, latest):
486
+ if _do_upgrade():
487
+ _log(f" Updated to {latest}.")
488
+ try:
489
+ import tkinter as tk
490
+ from tkinter import messagebox
491
+ root = tk.Tk(); root.withdraw()
492
+ messagebox.showinfo("Pro-ker", f"Updated to v{latest}.\n\nRestart to use the new version.")
493
+ root.destroy()
494
+ except Exception:
495
+ pass
496
+ except Exception:
497
+ pass
498
+ threading.Thread(target=_bg_update_check, daemon=True).start()
499
+
500
+ # Start server
501
+ if sys.platform == "win32":
502
+ import asyncio
503
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
504
+
505
+ import uvicorn
506
+ uvicorn.run(app, host=args.host, port=args.port, log_level="warning")
507
+ return 0