Coverage for src/asgi_profiler/__main__.py: 68%

33 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-22 16:59 +0000

1"""Serve the viewer against a `profiler.db` file, with no application. 

2 

3 python -m asgi_profiler profiler.db 

4 

5Capturing in staging and reading the file on your laptop should not require 

6booting the application that produced it. The viewer is already a 

7self-contained ASGI app over a `Storage`, so this is mostly argument parsing. 

8""" 

9 

10from __future__ import annotations 

11 

12import argparse 

13import sys 

14from pathlib import Path 

15 

16from .config import ProfilerConfig 

17from .storage import IncompatibleCapture, SQLiteStorage 

18from .viewer import build_viewer 

19 

20 

21def main(argv: list[str] | None = None) -> int: 

22 # Reachable two ways, and each needs a different usage line. Left to 

23 # argparse, `python -m asgi_profiler` would print `usage: __main__.py`, 

24 # because that is what `-m` puts in argv[0]; hard-coded, the console 

25 # script would print a command the user did not type. 

26 if Path(sys.argv[0]).stem == "__main__": 26 ↛ 27line 26 didn't jump to line 27 because the condition on line 26 was never true

27 prog = "python -m asgi_profiler" 

28 else: 

29 prog = "asgi-profiler" 

30 parser = argparse.ArgumentParser( 

31 prog=prog, 

32 description="Browse an asgi-profiler SQLite capture.", 

33 ) 

34 parser.add_argument("database", type=Path, help="path to a profiler.db file") 

35 parser.add_argument("--host", default="127.0.0.1") 

36 parser.add_argument("--port", type=int, default=8080) 

37 parser.add_argument("--page-size", type=int, default=50, help="rows per page (default: 50)") 

38 args = parser.parse_args(argv) 

39 

40 if not args.database.exists(): 

41 parser.error(f"no such file: {args.database}") 

42 

43 # The capture is checked before the server dependency, so a bad file is 

44 # reported as a bad file rather than as a missing uvicorn. 

45 # 

46 # Read-only: opening a capture to look at it must never be able to rewrite 

47 # or drop it. 

48 try: 

49 storage = SQLiteStorage(args.database, background=False, read_only=True) 

50 except IncompatibleCapture as exc: 

51 parser.error(str(exc)) 

52 

53 try: 

54 import uvicorn 

55 except ImportError: # pragma: no cover - depends on the environment 

56 storage.close() 

57 parser.error("uvicorn is required to serve the viewer: pip install uvicorn") 

58 

59 config = ProfilerConfig(mount_path="", page_size=args.page_size) 

60 app = build_viewer(storage, config) 

61 

62 print(f"{storage.count()} requests in {args.database}") 

63 print(f"viewer on http://{args.host}:{args.port}/") 

64 try: 

65 uvicorn.run(app, host=args.host, port=args.port, log_level="warning") 

66 finally: 

67 storage.close() 

68 return 0 

69 

70 

71if __name__ == "__main__": # pragma: no cover 

72 sys.exit(main())