watchdock-errors 0.2.4__tar.gz → 0.3.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.
Files changed (26) hide show
  1. {watchdock_errors-0.2.4/src/watchdock_errors.egg-info → watchdock_errors-0.3.0}/PKG-INFO +1 -1
  2. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/utils.py +32 -0
  3. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0/src/watchdock_errors.egg-info}/PKG-INFO +1 -1
  4. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors.egg-info/SOURCES.txt +2 -1
  5. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors.egg-info/scm_file_list.json +9 -8
  6. watchdock_errors-0.3.0/src/watchdock_errors.egg-info/scm_version.json +8 -0
  7. watchdock_errors-0.3.0/tests/test_utils.py +44 -0
  8. watchdock_errors-0.2.4/src/watchdock_errors.egg-info/scm_version.json +0 -8
  9. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/.github/workflows/ci.yml +0 -0
  10. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/.github/workflows/release.yml +0 -0
  11. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/.gitignore +0 -0
  12. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/README.md +0 -0
  13. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/pyproject.toml +0 -0
  14. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/setup.cfg +0 -0
  15. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/__init__.py +0 -0
  16. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/client.py +0 -0
  17. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/config.py +0 -0
  18. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/event.py +0 -0
  19. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/integrations/__init__.py +0 -0
  20. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/integrations/django.py +0 -0
  21. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors/integrations/fastapi.py +0 -0
  22. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors.egg-info/dependency_links.txt +0 -0
  23. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors.egg-info/requires.txt +0 -0
  24. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/src/watchdock_errors.egg-info/top_level.txt +0 -0
  25. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/tests/test_client.py +0 -0
  26. {watchdock_errors-0.2.4 → watchdock_errors-0.3.0}/tests/test_event.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: watchdock-errors
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Application error tracking SDK for the Watchdock platform
5
5
  License: MIT
6
6
  Keywords: error-tracking,observability,watchdock
@@ -6,6 +6,35 @@ import socket
6
6
  import traceback
7
7
 
8
8
 
9
+ #: Number of source lines captured on each side of the failing line. Matches the
10
+ #: window the backend uses when it falls back to enriching frames itself, so a
11
+ #: frame looks the same whether the context came from here or from the server.
12
+ CONTEXT_LINES = 2
13
+
14
+
15
+ def _source_context(filename: str, lineno: int) -> tuple[list[str], list[str]]:
16
+ """Read the source lines immediately before and after ``lineno``.
17
+
18
+ Returns ``(pre_context, post_context)``. We do this in the SDK, on the box
19
+ where the code actually runs, because the source is here — the backend can
20
+ only guess at it from its own filesystem, which is the wrong machine for any
21
+ real app. Out-of-range lines come back as empty strings so the two lists are
22
+ always the same length regardless of where in the file the error landed.
23
+ """
24
+ if not filename or not lineno:
25
+ return [], []
26
+
27
+ pre = [
28
+ linecache.getline(filename, i).rstrip("\n")
29
+ for i in range(max(1, lineno - CONTEXT_LINES), lineno)
30
+ ]
31
+ post = [
32
+ linecache.getline(filename, i).rstrip("\n")
33
+ for i in range(lineno + 1, lineno + CONTEXT_LINES + 1)
34
+ ]
35
+ return pre, post
36
+
37
+
9
38
  def extract_stacktrace(exc: BaseException) -> list[dict]:
10
39
  """Extract structured stack frames from an exception."""
11
40
  tb = exc.__traceback__
@@ -15,12 +44,15 @@ def extract_stacktrace(exc: BaseException) -> list[dict]:
15
44
  frames = []
16
45
  for frame_summary in traceback.extract_tb(tb):
17
46
  context_line = linecache.getline(frame_summary.filename, frame_summary.lineno).strip()
47
+ pre_context, post_context = _source_context(frame_summary.filename, frame_summary.lineno)
18
48
  frames.append(
19
49
  {
20
50
  "filename": frame_summary.filename,
21
51
  "function": frame_summary.name,
22
52
  "lineno": frame_summary.lineno,
23
53
  "context_line": context_line,
54
+ "pre_context": pre_context,
55
+ "post_context": post_context,
24
56
  }
25
57
  )
26
58
  return frames
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: watchdock-errors
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Application error tracking SDK for the Watchdock platform
5
5
  License: MIT
6
6
  Keywords: error-tracking,observability,watchdock
@@ -19,4 +19,5 @@ src/watchdock_errors/integrations/__init__.py
19
19
  src/watchdock_errors/integrations/django.py
20
20
  src/watchdock_errors/integrations/fastapi.py
21
21
  tests/test_client.py
22
- tests/test_event.py
22
+ tests/test_event.py
23
+ tests/test_utils.py
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "files": [
3
+ ".gitignore",
3
4
  "README.md",
4
5
  "pyproject.toml",
5
- ".gitignore",
6
- "src/watchdock_errors/__init__.py",
6
+ "src/watchdock_errors/event.py",
7
+ "src/watchdock_errors/client.py",
7
8
  "src/watchdock_errors/config.py",
8
9
  "src/watchdock_errors/utils.py",
9
- "src/watchdock_errors/client.py",
10
- "src/watchdock_errors/event.py",
11
- "src/watchdock_errors/integrations/__init__.py",
10
+ "src/watchdock_errors/__init__.py",
12
11
  "src/watchdock_errors/integrations/fastapi.py",
13
12
  "src/watchdock_errors/integrations/django.py",
14
- "tests/test_client.py",
15
- "tests/test_event.py",
13
+ "src/watchdock_errors/integrations/__init__.py",
14
+ ".github/workflows/ci.yml",
16
15
  ".github/workflows/release.yml",
17
- ".github/workflows/ci.yml"
16
+ "tests/test_event.py",
17
+ "tests/test_client.py",
18
+ "tests/test_utils.py"
18
19
  ]
19
20
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.3.0",
3
+ "distance": 0,
4
+ "node": "g711d547840b425c8a17c295e36cd0e731bdadbc4",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-22"
8
+ }
@@ -0,0 +1,44 @@
1
+ from watchdock_errors.utils import extract_stacktrace
2
+
3
+
4
+ def _boom():
5
+ # This line and the two on either side are what the context capture should
6
+ # pick up. Keep the surrounding lines stable so the assertions below hold.
7
+ x = 1
8
+ raise ValueError("boom") # <-- failing line
9
+ return x
10
+
11
+
12
+ def test_extract_stacktrace_captures_source_context():
13
+ try:
14
+ _boom()
15
+ except ValueError as exc:
16
+ frames = extract_stacktrace(exc)
17
+
18
+ assert frames, "expected at least one frame"
19
+
20
+ # The frame for _boom is the one whose failing line raised.
21
+ boom_frame = next(f for f in frames if f["function"] == "_boom")
22
+
23
+ assert boom_frame["context_line"] == 'raise ValueError("boom") # <-- failing line'
24
+ # Two lines before and two after, in order, with indentation preserved.
25
+ assert len(boom_frame["pre_context"]) == 2
26
+ assert len(boom_frame["post_context"]) == 2
27
+ # The line immediately before the raise is `x = 1` (indentation kept).
28
+ assert boom_frame["pre_context"][1] == " x = 1"
29
+ # The line immediately after the raise is the return.
30
+ assert boom_frame["post_context"][0] == " return x"
31
+
32
+
33
+ def test_frames_always_have_context_keys():
34
+ try:
35
+ raise KeyError("missing")
36
+ except KeyError as exc:
37
+ frames = extract_stacktrace(exc)
38
+
39
+ for frame in frames:
40
+ assert "context_line" in frame
41
+ assert "pre_context" in frame
42
+ assert "post_context" in frame
43
+ assert isinstance(frame["pre_context"], list)
44
+ assert isinstance(frame["post_context"], list)
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.2.4",
3
- "distance": 0,
4
- "node": "g8207b34ddbb2d7b5c9fe4f4df0960161f5b11709",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-11"
8
- }