watchman-sdk 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,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: watchman-sdk
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.9
5
+ Requires-Dist: httpx>=0.27.0
@@ -0,0 +1,9 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "watchman-sdk"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.9"
9
+ dependencies = ["httpx>=0.27.0"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ import sys, os
2
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
3
+ from unittest.mock import patch, MagicMock
4
+ from watchman_sdk.client import WatchmanClient
5
+
6
+ def test_send_heartbeat_success():
7
+ client = WatchmanClient("test-svc", "token123", "http://localhost:8000")
8
+ mock_resp = MagicMock()
9
+ mock_resp.status_code = 200
10
+ with patch("httpx.post", return_value=mock_resp):
11
+ result = client.send_heartbeat()
12
+ assert result is True
13
+
14
+ def test_send_heartbeat_failure():
15
+ client = WatchmanClient("test-svc", "token123", "http://localhost:8000")
16
+ with patch("httpx.post", side_effect=Exception("Connection refused")):
17
+ result = client.send_heartbeat()
18
+ assert result is False
19
+
20
+ def test_send_event():
21
+ client = WatchmanClient("test-svc", "token123", "http://localhost:8000")
22
+ mock_resp = MagicMock()
23
+ mock_resp.status_code = 200
24
+ with patch("httpx.post", return_value=mock_resp) as mock_post:
25
+ result = client.send_event("error", "critical", "Something failed")
26
+ assert result is True
27
+ call_args = mock_post.call_args
28
+ assert call_args[1]["json"]["type"] == "error"
@@ -0,0 +1,39 @@
1
+ import functools
2
+ from watchman_sdk.client import WatchmanClient
3
+ from watchman_sdk.heartbeat import HeartbeatThread
4
+ from watchman_sdk.error_capture import setup_error_capture
5
+ from typing import Optional
6
+
7
+ _client: Optional[WatchmanClient] = None
8
+ _heartbeat: Optional[HeartbeatThread] = None
9
+
10
+ def init(token: str, core_url: str, service: str = None, heartbeat_interval: int = 60) -> None:
11
+ global _client, _heartbeat
12
+ _client = WatchmanClient(service=service, token=token, core=core_url)
13
+ setup_error_capture(_client)
14
+ _heartbeat = HeartbeatThread(_client, interval_seconds=heartbeat_interval)
15
+ _heartbeat.start()
16
+
17
+ def send_event(type: str, severity: str, message: str, meta: dict = None) -> bool:
18
+ if _client is None:
19
+ raise RuntimeError("watchman.init() must be called first")
20
+ return _client.send_event(type=type, severity=severity, message=message, meta=meta)
21
+
22
+ def check(name: str):
23
+ """Decorator for check functions."""
24
+ def decorator(func):
25
+ @functools.wraps(func)
26
+ def wrapper(*args, **kwargs):
27
+ if _client is None:
28
+ raise RuntimeError("watchman.init() must be called first")
29
+ result = func(*args, **kwargs)
30
+ ok = result.get("ok", False)
31
+ _client.send_event(
32
+ type="check",
33
+ severity="critical" if not ok else "info",
34
+ message=f"Check {name}: {'ok' if ok else 'failed'}",
35
+ meta={**result, "check_name": name},
36
+ )
37
+ return result
38
+ return wrapper
39
+ return decorator
@@ -0,0 +1,47 @@
1
+ import httpx
2
+ from typing import Optional, Any
3
+
4
+ class WatchmanClient:
5
+ def __init__(self, service: str, token: str, core: str):
6
+ self.service = service
7
+ self.token = token
8
+ self.core = core.rstrip("/")
9
+
10
+ def _headers(self) -> dict:
11
+ return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
12
+
13
+ def send_heartbeat(self, version: Optional[str] = None) -> bool:
14
+ try:
15
+ resp = httpx.post(
16
+ f"{self.core}/api/heartbeat",
17
+ json={"service": self.service, "status": "alive", "version": version},
18
+ headers=self._headers(),
19
+ timeout=5.0,
20
+ )
21
+ return resp.status_code == 200
22
+ except Exception:
23
+ return False
24
+
25
+ def send_event(
26
+ self,
27
+ type: str,
28
+ severity: str,
29
+ message: str,
30
+ meta: Optional[dict[str, Any]] = None,
31
+ ) -> bool:
32
+ try:
33
+ resp = httpx.post(
34
+ f"{self.core}/api/events",
35
+ json={
36
+ "service": self.service,
37
+ "type": type,
38
+ "severity": severity,
39
+ "message": message,
40
+ "meta": meta or {},
41
+ },
42
+ headers=self._headers(),
43
+ timeout=5.0,
44
+ )
45
+ return resp.status_code == 200
46
+ except Exception:
47
+ return False
@@ -0,0 +1,17 @@
1
+ import sys
2
+ import traceback
3
+
4
+ def setup_error_capture(client) -> None:
5
+ original_hook = sys.excepthook
6
+
7
+ def custom_excepthook(exc_type, exc_value, exc_tb):
8
+ tb_str = "".join(traceback.format_tb(exc_tb))
9
+ client.send_event(
10
+ type="error",
11
+ severity="critical",
12
+ message=f"{exc_type.__name__}: {exc_value}",
13
+ meta={"traceback": tb_str},
14
+ )
15
+ original_hook(exc_type, exc_value, exc_tb)
16
+
17
+ sys.excepthook = custom_excepthook
@@ -0,0 +1,16 @@
1
+ import threading
2
+
3
+ class HeartbeatThread(threading.Thread):
4
+ def __init__(self, client, interval_seconds: int = 60):
5
+ super().__init__(daemon=True)
6
+ self.client = client
7
+ self.interval = interval_seconds
8
+ self._stop_event = threading.Event()
9
+
10
+ def run(self) -> None:
11
+ while not self._stop_event.is_set():
12
+ self.client.send_heartbeat()
13
+ self._stop_event.wait(self.interval)
14
+
15
+ def stop(self) -> None:
16
+ self._stop_event.set()
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: watchman-sdk
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.9
5
+ Requires-Dist: httpx>=0.27.0
@@ -0,0 +1,11 @@
1
+ pyproject.toml
2
+ tests/test_client.py
3
+ watchman_sdk/__init__.py
4
+ watchman_sdk/client.py
5
+ watchman_sdk/error_capture.py
6
+ watchman_sdk/heartbeat.py
7
+ watchman_sdk.egg-info/PKG-INFO
8
+ watchman_sdk.egg-info/SOURCES.txt
9
+ watchman_sdk.egg-info/dependency_links.txt
10
+ watchman_sdk.egg-info/requires.txt
11
+ watchman_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ httpx>=0.27.0
@@ -0,0 +1 @@
1
+ watchman_sdk