flowwatch-client 2.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,76 @@
1
+ # Dependencies
2
+ node_modules/
3
+ **/node_modules/
4
+
5
+ # Build output
6
+ dist/
7
+ build/
8
+ out/
9
+ coverage/
10
+ *.tsbuildinfo
11
+
12
+ # Package manager caches
13
+ .npm/
14
+ .pnpm-store/
15
+ .yarn/
16
+ !.yarn/releases/
17
+ !.yarn/plugins/
18
+ !.yarn/sdks/
19
+ !.yarn/versions/
20
+
21
+ # Logs
22
+ logs/
23
+ *.log
24
+ npm-debug.log*
25
+ yarn-debug.log*
26
+ yarn-error.log*
27
+ pnpm-debug.log*
28
+
29
+ # Environment and local secrets
30
+ .env
31
+ .env.*
32
+ !.env.example
33
+ !.env*.example
34
+ .fw.env
35
+
36
+ # Test app (internal development only)
37
+ apps/test-app/
38
+ apps/
39
+
40
+ # Local infra data
41
+ infra/data/
42
+ infra/.data/
43
+ docker-data/
44
+ postgres-data/
45
+ redis-data/
46
+ elasticsearch-data/
47
+ flowwatch_npm_project_details.md
48
+ PROJECT_HANDOFF.md
49
+ docs/
50
+
51
+ # Runtime temp files
52
+ tmp/
53
+ temp/
54
+ .cache/
55
+
56
+ # Test and tooling caches
57
+ .nyc_output/
58
+ .vitest/
59
+ .jest/
60
+ .eslintcache
61
+
62
+ # Framework/tool output
63
+ .vite/
64
+ .turbo/
65
+ .next/
66
+
67
+ # IDE/editor
68
+ .vscode/
69
+ .idea/
70
+ *.swp
71
+ *.swo
72
+
73
+ # OS files
74
+ .DS_Store
75
+ Thumbs.db
76
+ desktop.ini
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: flowwatch-client
3
+ Version: 2.1.0
4
+ Summary: Python client SDK for FlowWatch — feature flags, durable workflows, tracing, and error capture
5
+ Project-URL: Homepage, https://github.com/PranshulSoni/flowwatch
6
+ Project-URL: Issues, https://github.com/PranshulSoni/flowwatch/issues
7
+ License: ISC
8
+ Requires-Python: >=3.8
9
+ Requires-Dist: httpx>=0.27
10
+ Description-Content-Type: text/markdown
11
+
12
+ # flowwatch (Python)
13
+
14
+ Python client for [FlowWatch](https://github.com/PranshulSoni/flowwatch). Wraps the FlowWatch sidecar REST API — requires the [sidecar server](https://github.com/PranshulSoni/flowwatch#multi-language-support-sidecar-server) to be running.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install flowwatch-client
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```python
25
+ from flowwatch import FlowwatchClient
26
+
27
+ client = FlowwatchClient("http://localhost:9400", token="your-sidecar-token")
28
+
29
+ # Feature flag
30
+ if client.evaluate_flag("new-billing-flow", {"userId": "u123", "plan": "pro"}):
31
+ use_new_flow()
32
+
33
+ # Durable workflow
34
+ result = client.trigger_workflow("checkout", {"cartId": "cart_1"})
35
+ print(result["executionId"])
36
+
37
+ # Auto-timed trace span
38
+ with client.trace_span("process-order", "function"):
39
+ process_order()
40
+
41
+ # Error capture
42
+ try:
43
+ risky()
44
+ except Exception as e:
45
+ import traceback
46
+ client.capture_error(str(e), stack=traceback.format_exc(), source="my-service")
47
+
48
+ # Health check
49
+ print(client.health())
50
+
51
+ client.close()
52
+ ```
53
+
54
+ ### Async
55
+
56
+ ```python
57
+ from flowwatch import AsyncFlowwatchClient
58
+
59
+ async with AsyncFlowwatchClient("http://localhost:9400", token="your-token") as client:
60
+ enabled = await client.evaluate_flag("dark-mode", {"userId": "u1"})
61
+ result = await client.trigger_workflow("send-email", {"to": "user@example.com"})
62
+ ```
@@ -0,0 +1,51 @@
1
+ # flowwatch (Python)
2
+
3
+ Python client for [FlowWatch](https://github.com/PranshulSoni/flowwatch). Wraps the FlowWatch sidecar REST API — requires the [sidecar server](https://github.com/PranshulSoni/flowwatch#multi-language-support-sidecar-server) to be running.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install flowwatch-client
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from flowwatch import FlowwatchClient
15
+
16
+ client = FlowwatchClient("http://localhost:9400", token="your-sidecar-token")
17
+
18
+ # Feature flag
19
+ if client.evaluate_flag("new-billing-flow", {"userId": "u123", "plan": "pro"}):
20
+ use_new_flow()
21
+
22
+ # Durable workflow
23
+ result = client.trigger_workflow("checkout", {"cartId": "cart_1"})
24
+ print(result["executionId"])
25
+
26
+ # Auto-timed trace span
27
+ with client.trace_span("process-order", "function"):
28
+ process_order()
29
+
30
+ # Error capture
31
+ try:
32
+ risky()
33
+ except Exception as e:
34
+ import traceback
35
+ client.capture_error(str(e), stack=traceback.format_exc(), source="my-service")
36
+
37
+ # Health check
38
+ print(client.health())
39
+
40
+ client.close()
41
+ ```
42
+
43
+ ### Async
44
+
45
+ ```python
46
+ from flowwatch import AsyncFlowwatchClient
47
+
48
+ async with AsyncFlowwatchClient("http://localhost:9400", token="your-token") as client:
49
+ enabled = await client.evaluate_flag("dark-mode", {"userId": "u1"})
50
+ result = await client.trigger_workflow("send-email", {"to": "user@example.com"})
51
+ ```
@@ -0,0 +1,3 @@
1
+ from .client import AsyncFlowwatchClient, FlowwatchClient
2
+
3
+ __all__ = ["FlowwatchClient", "AsyncFlowwatchClient"]
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from contextlib import contextmanager
5
+ from typing import Any, Generator
6
+
7
+ import httpx
8
+
9
+
10
+ class FlowwatchClient:
11
+ """Synchronous FlowWatch sidecar client. Use as a context manager for connection cleanup."""
12
+
13
+ def __init__(self, base_url: str = "http://localhost:9400", token: str | None = None) -> None:
14
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
15
+ self._http = httpx.Client(base_url=base_url, headers=headers)
16
+
17
+ def evaluate_flag(self, key: str, context: dict[str, Any] | None = None) -> bool:
18
+ r = self._http.post("/api/flag", json={"key": key, "context": context or {}})
19
+ r.raise_for_status()
20
+ return bool(r.json().get("enabled", False))
21
+
22
+ def trigger_workflow(self, name: str, input: Any = None) -> dict[str, Any]:
23
+ r = self._http.post("/api/trigger", json={"name": name, "input": input})
24
+ r.raise_for_status()
25
+ return r.json()
26
+
27
+ def log_trace_span(
28
+ self,
29
+ name: str,
30
+ type: str,
31
+ duration_ms: float,
32
+ status: str = "ok",
33
+ metadata: dict[str, Any] | None = None,
34
+ ) -> None:
35
+ r = self._http.post(
36
+ "/api/trace-span",
37
+ json={"name": name, "type": type, "durationMs": duration_ms, "status": status, "metadata": metadata},
38
+ )
39
+ r.raise_for_status()
40
+
41
+ def capture_error(
42
+ self,
43
+ message: str,
44
+ name: str = "Error",
45
+ stack: str | None = None,
46
+ source: str | None = None,
47
+ **options: Any,
48
+ ) -> None:
49
+ r = self._http.post(
50
+ "/api/capture-error",
51
+ json={
52
+ "error": {"message": message, "name": name, "stack": stack},
53
+ "options": {"source": source, **options},
54
+ },
55
+ )
56
+ r.raise_for_status()
57
+
58
+ def health(self) -> dict[str, Any]:
59
+ r = self._http.get("/api/health")
60
+ r.raise_for_status()
61
+ return r.json()
62
+
63
+ @contextmanager
64
+ def trace_span(
65
+ self, name: str, type: str, metadata: dict[str, Any] | None = None
66
+ ) -> Generator[None, None, None]:
67
+ """Auto-times the wrapped block and submits a trace span on exit."""
68
+ start = time.monotonic()
69
+ status = "ok"
70
+ try:
71
+ yield
72
+ except Exception:
73
+ status = "error"
74
+ raise
75
+ finally:
76
+ self.log_trace_span(name, type, (time.monotonic() - start) * 1000, status, metadata)
77
+
78
+ def close(self) -> None:
79
+ self._http.close()
80
+
81
+ def __enter__(self) -> FlowwatchClient:
82
+ return self
83
+
84
+ def __exit__(self, *_: Any) -> None:
85
+ self.close()
86
+
87
+
88
+ class AsyncFlowwatchClient:
89
+ """Async FlowWatch sidecar client. Use as an async context manager for connection cleanup."""
90
+
91
+ def __init__(self, base_url: str = "http://localhost:9400", token: str | None = None) -> None:
92
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
93
+ self._http = httpx.AsyncClient(base_url=base_url, headers=headers)
94
+
95
+ async def evaluate_flag(self, key: str, context: dict[str, Any] | None = None) -> bool:
96
+ r = await self._http.post("/api/flag", json={"key": key, "context": context or {}})
97
+ r.raise_for_status()
98
+ return bool(r.json().get("enabled", False))
99
+
100
+ async def trigger_workflow(self, name: str, input: Any = None) -> dict[str, Any]:
101
+ r = await self._http.post("/api/trigger", json={"name": name, "input": input})
102
+ r.raise_for_status()
103
+ return r.json()
104
+
105
+ async def log_trace_span(
106
+ self,
107
+ name: str,
108
+ type: str,
109
+ duration_ms: float,
110
+ status: str = "ok",
111
+ metadata: dict[str, Any] | None = None,
112
+ ) -> None:
113
+ r = await self._http.post(
114
+ "/api/trace-span",
115
+ json={"name": name, "type": type, "durationMs": duration_ms, "status": status, "metadata": metadata},
116
+ )
117
+ r.raise_for_status()
118
+
119
+ async def capture_error(
120
+ self,
121
+ message: str,
122
+ name: str = "Error",
123
+ stack: str | None = None,
124
+ source: str | None = None,
125
+ **options: Any,
126
+ ) -> None:
127
+ r = await self._http.post(
128
+ "/api/capture-error",
129
+ json={
130
+ "error": {"message": message, "name": name, "stack": stack},
131
+ "options": {"source": source, **options},
132
+ },
133
+ )
134
+ r.raise_for_status()
135
+
136
+ async def health(self) -> dict[str, Any]:
137
+ r = await self._http.get("/api/health")
138
+ r.raise_for_status()
139
+ return r.json()
140
+
141
+ async def aclose(self) -> None:
142
+ await self._http.aclose()
143
+
144
+ async def __aenter__(self) -> AsyncFlowwatchClient:
145
+ return self
146
+
147
+ async def __aexit__(self, *_: Any) -> None:
148
+ await self.aclose()
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "flowwatch-client"
3
+ version = "2.1.0"
4
+ description = "Python client SDK for FlowWatch — feature flags, durable workflows, tracing, and error capture"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ license = { text = "ISC" }
8
+ dependencies = ["httpx>=0.27"]
9
+
10
+ [project.urls]
11
+ Homepage = "https://github.com/PranshulSoni/flowwatch"
12
+ Issues = "https://github.com/PranshulSoni/flowwatch/issues"
13
+
14
+ [build-system]
15
+ requires = ["hatchling"]
16
+ build-backend = "hatchling.build"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["flowwatch"]