streamgine 1.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,47 @@
1
+ node_modules/
2
+ apps/web/node_modules/
3
+ apps/web/dist/
4
+
5
+ # Vite build output (apps/web → public/)
6
+ public/assets/
7
+ public/index.html
8
+ public/favicon.svg
9
+ public/icons.svg
10
+
11
+ !.env.development.example
12
+ .DS_Store
13
+
14
+ # Local VS Code / Cursor multi-root workspace
15
+ diff-events.code-workspace
16
+
17
+ # Cursor agent skills (local Stripe tooling; reinstall per machine)
18
+ skills/
19
+ skills-lock.json
20
+ # Shared Cursor workflow skills are versioned, unlike the per-machine ones above
21
+ !.cursor-personal-configs/skills/
22
+
23
+ # Python client
24
+ clients/python/.pytest_cache/
25
+ .pytest_cache/
26
+ clients/python/.venv/
27
+ clients/python/dist/
28
+ clients/python/build/
29
+ clients/python/**/*.egg-info/
30
+ clients/python/**/__pycache__/
31
+ tools/python/**/__pycache__/
32
+
33
+ # Miss-rate / load datasets (generated locally)
34
+ tmp/
35
+
36
+ # Accidental root venv (use clients/python/.venv)
37
+ .venv/
38
+
39
+ # Stripe Projects (local; CLI-managed)
40
+ .projects/
41
+ .claude/
42
+ CLAUDE.md
43
+ .cursorignore
44
+ .env
45
+ .env.*
46
+ !.env.development.example
47
+ !.env.example
@@ -0,0 +1,323 @@
1
+ Metadata-Version: 2.5
2
+ Name: streamgine
3
+ Version: 1.0.0
4
+ Summary: Streamgine Python SDK — Diff by Streamgine webhooks client and future modules.
5
+ Project-URL: Homepage, https://streamgine.com
6
+ Project-URL: Documentation, https://diff.streamgine.com/docs.md
7
+ Project-URL: Repository, https://github.com/scurtutech/diff-events-service
8
+ Project-URL: Issues, https://github.com/scurtutech/diff-events-service/issues
9
+ Project-URL: Changelog, https://github.com/scurtutech/diff-events-service/releases
10
+ Author-email: Streamgine <hello@streamgine.com>
11
+ License: MIT
12
+ Keywords: agents,compliance,corporate-standing,diff,streamgine,webhooks
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.27.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # streamgine — Python SDK
30
+
31
+ **Diff by Streamgine** lives under the `diff` module. Install once; add more Streamgine modules (training data, etc.) under the same package later.
32
+
33
+ This directory is the **customer package** published to PyPI. Repo-only load tests and dev agents live in [`tools/python/`](../../tools/python/README.md) — not shipped to customers.
34
+
35
+ **Today:** one wheel `streamgine` = Diff (`from streamgine import diff`). **Next products** (e.g. training-data) get their own wheels (`streamgine-diff`, `streamgine-training`, …) under the same `streamgine` import namespace — see [PUBLISHING.md](PUBLISHING.md) and `.cursor/rules/python-client.mdc`. Do not grow a fat single package.
36
+
37
+ **Product:** [diff.streamgine.com](https://diff.streamgine.com) · **Docs:** [docs.md](https://diff.streamgine.com/docs.md) · **California:** [coverage](https://diff.streamgine.com/california) · **Use cases:** [risk monitoring](https://diff.streamgine.com/use-cases/risk-monitoring), [AI agents](https://diff.streamgine.com/use-cases/ai-agents) · **Repo:** [github.com/scurtutech/diff-events-service](https://github.com/scurtutech/diff-events-service)
38
+
39
+ ```python
40
+ from streamgine import diff
41
+
42
+ # Once at startup — pick ONE download from /account:
43
+ diff.configure("diff-streamgine-credentials.json")
44
+ # OR: source diff-streamgine-env.sh then diff.configure()
45
+ client = diff.DiffClient()
46
+ ```
47
+
48
+ Install from PyPI:
49
+
50
+ ```bash
51
+ python3 -m pip install streamgine
52
+ ```
53
+
54
+ Or from this repo:
55
+
56
+ ```bash
57
+ cd clients/python
58
+ python3 -m venv .venv && source .venv/bin/activate
59
+ python -m pip install .
60
+ ```
61
+
62
+ Requires Python 3.10+. One dependency: [httpx](https://www.python-httpx.org/) (TLS verification enabled by default).
63
+
64
+ ## Credentials (one setup point)
65
+
66
+ Call `diff.configure()` **once** at process startup. Every `DiffClient()` and `verify_webhook()` reads from that config.
67
+
68
+ Download from `/account` or checkout — filenames are fixed:
69
+
70
+ | Download | Then |
71
+ | ---------------------------------- | ------------------------------------------------------- |
72
+ | `diff-streamgine-credentials.json` | `diff.configure("diff-streamgine-credentials.json")` |
73
+ | `diff-streamgine-env.sh` | `source diff-streamgine-env.sh` then `diff.configure()` |
74
+
75
+ **Option A — credentials JSON:**
76
+
77
+ ```python
78
+ diff.configure("diff-streamgine-credentials.json")
79
+ ```
80
+
81
+ **Option B — env script:**
82
+
83
+ ```bash
84
+ source diff-streamgine-env.sh
85
+ ```
86
+
87
+ ```python
88
+ diff.configure()
89
+ ```
90
+
91
+ Do **not** pass API keys or signing secrets to `DiffClient()` or `verify_webhook()` in normal use.
92
+
93
+ ## Zero-setup agent (copy-paste)
94
+
95
+ ```bash
96
+ python3 -m venv .venv
97
+ source .venv/bin/activate
98
+ python -m pip install -e . fastapi uvicorn
99
+ # Customer path: download from /account, then ONE of:
100
+ # source diff-streamgine-env.sh
101
+ # # or keep diff-streamgine-credentials.json next to the process
102
+ # Local repo seed (AUTH_ENABLED): npm run seed:dev-customer, then export printed keys
103
+ source diff-streamgine-env.sh # or configure("diff-streamgine-credentials.json") in code
104
+ python -m uvicorn examples.agent:app --port 8000
105
+ ```
106
+
107
+ Runnable source: [`examples/agent.py`](examples/agent.py). Calls `register_test()` on startup; within ~1s you should see `after={'test': 'success'}` on topic `diff-event.test`.
108
+
109
+ **Serverless (AWS Lambda):** [`examples/serverless/`](examples/serverless/README.md) — deploy `handler.py`, run `register.py` once, receive test heartbeats in CloudWatch.
110
+
111
+ **Agent journey (production filters):** [`examples/agent_journey.py`](examples/agent_journey.py).
112
+
113
+ Docker worker reaching a host agent:
114
+
115
+ ```bash
116
+ export DIFF_CALLBACK_URL=http://host.docker.internal:8000/webhooks/diff
117
+ ```
118
+
119
+ ## Test heartbeats (start here)
120
+
121
+ Before wiring real registry events, confirm your agent receives signed webhooks:
122
+
123
+ ```python
124
+ from streamgine import diff
125
+
126
+ diff.configure() # after: source diff-streamgine-env.sh OR configure("diff-streamgine-credentials.json")
127
+ client = diff.DiffClient()
128
+ client.register_test(callback_url="https://your-agent.example/webhooks/diff")
129
+ ```
130
+
131
+ Filter on **`event.is_test_heartbeat`** (or `after == {"test": "success"}`). The service sends this every second:
132
+
133
+ ```json
134
+ {
135
+ "action": "INSERT",
136
+ "after": { "test": "success" }
137
+ }
138
+ ```
139
+
140
+ No `entity_id` on test events.
141
+
142
+ ## Quick start (production registry events)
143
+
144
+ ```python
145
+ from streamgine import diff
146
+
147
+ diff.configure()
148
+ client = diff.DiffClient()
149
+ client.register(
150
+ match={
151
+ "state": "CA",
152
+ "principal_city": "San Francisco",
153
+ },
154
+ actions=["INSERT"],
155
+ callback_url="https://your-agent.example/webhooks/diff",
156
+ )
157
+ client.close()
158
+ ```
159
+
160
+ `INSERT` = newly added company, `UPDATE` = changed, `DELETE` = removed. Omit `actions` to receive every matching state/city event.
161
+
162
+ ### Search current companies (full story)
163
+
164
+ Webhooks deliver changes. Search returns **current** snapshots:
165
+
166
+ ```python
167
+ from streamgine import diff
168
+
169
+ diff.configure()
170
+ client = diff.DiffClient()
171
+ result = client.search(
172
+ query={"match": {"principalCity": "San Francisco"}},
173
+ size=10,
174
+ )
175
+ for company in result["entities"]:
176
+ print(company.get("entity_name"), company.get("entity_number"))
177
+ ```
178
+
179
+ Agent journey: **Search → Watch → Qualify → Act → Stay current** (see [`/docs.md`](../../public/docs.md)).
180
+
181
+ ### 2. Verify inbound webhooks (required)
182
+
183
+ Always verify **raw request bytes** before trusting JSON. The worker signs the exact JSON body with HMAC-SHA256.
184
+
185
+ ```python
186
+ from streamgine import diff
187
+
188
+ diff.configure()
189
+
190
+ def handle_webhook(raw_body: bytes, signature: str | None):
191
+ try:
192
+ event = diff.verify_webhook(raw_body, signature)
193
+ except diff.WebhookVerificationError:
194
+ return 401, {"error": "invalid signature"}
195
+
196
+ print(event.action, event.entity_id, event.after)
197
+ return 200, {"ok": True}
198
+ ```
199
+
200
+ **FastAPI**
201
+
202
+ ```python
203
+ from fastapi import FastAPI, Header, Request, Response
204
+
205
+ from streamgine import diff
206
+
207
+ app = FastAPI()
208
+
209
+ @app.on_event("startup")
210
+ def setup() -> None:
211
+ diff.configure()
212
+
213
+ @app.post("/webhooks/diff")
214
+ async def diff_webhook(
215
+ request: Request,
216
+ response: Response,
217
+ x_diff_signature: str | None = Header(default=None, alias="X-Diff-Signature"),
218
+ ):
219
+ raw = await request.body()
220
+ try:
221
+ event = diff.verify_webhook(raw, x_diff_signature)
222
+ except diff.WebhookVerificationError:
223
+ response.status_code = 401
224
+ return {"error": "invalid signature"}
225
+
226
+ # Your agent logic here
227
+ return {"ok": True, "entity_id": event.entity_id}
228
+ ```
229
+
230
+ **Flask**
231
+
232
+ ```python
233
+ from flask import Flask, request
234
+
235
+ from streamgine import diff
236
+
237
+ app = Flask(__name__)
238
+ diff.configure()
239
+
240
+ @app.post("/webhooks/diff")
241
+ def webhook():
242
+ try:
243
+ event = diff.verify_webhook(
244
+ request.get_data(),
245
+ request.headers.get("X-Diff-Signature"),
246
+ )
247
+ except diff.WebhookVerificationError:
248
+ return {"error": "invalid signature"}, 401
249
+
250
+ return {"ok": True, "action": event.action}
251
+ ```
252
+
253
+ ## Security
254
+
255
+ | Practice | How this client helps |
256
+ | ---------------------------- | --------------------------------------------------------------------------------------------- |
257
+ | Verify every webhook | `diff.verify_webhook()` uses `hmac.compare_digest` (timing-safe) on the raw body |
258
+ | Match worker algorithm | Same as Node: `HMAC-SHA256(secret, raw_json_bytes)` → header `X-Diff-Signature: sha256=<hex>` |
259
+ | Parse JSON only after verify | Tampered bodies fail before `WebhookEvent` is built |
260
+ | HTTPS in production | `DiffClient(verify_tls=True)` (default); use `https://` callback URLs |
261
+ | Keep secrets out of code | One `configure()` from JSON or env — never scatter keys in constructors |
262
+
263
+ Never log the signing secret or skip signature verification in production.
264
+
265
+ ### Credentials file (after checkout)
266
+
267
+ Download **`diff-streamgine-credentials.json`** (or **`diff-streamgine-env.sh`**) from `/account` / checkout. JSON format (version `1`):
268
+
269
+ ```json
270
+ {
271
+ "version": 1,
272
+ "service": "diff.streamgine.com",
273
+ "customer_id": "cus_…",
274
+ "api_key": "de_live_…",
275
+ "signing_secret": "whsec_…",
276
+ "api_url": "https://diff.streamgine.com",
277
+ "env": {
278
+ "DIFF_API_KEY": "de_live_…",
279
+ "DIFF_SIGNING_SECRET": "whsec_…",
280
+ "DIFF_API_URL": "https://diff.streamgine.com"
281
+ }
282
+ }
283
+ ```
284
+
285
+ ```python
286
+ from streamgine import diff
287
+
288
+ diff.configure("diff-streamgine-credentials.json")
289
+ client = diff.DiffClient()
290
+ # diff.verify_webhook(...) uses the same signing secret automatically
291
+ ```
292
+
293
+ ## Auth
294
+
295
+ | Env (via `configure()`) | Used for |
296
+ | ----------------------- | --------------------------------------------------------------- |
297
+ | `DIFF_API_KEY` | `register()` / `register_test()` (customer Bearer) |
298
+ | `DIFF_PROVIDER_API_KEY` | `get_state()` and provider ingest when provider auth is enabled |
299
+
300
+ Register responses are slim (`ok` / subscription fields only — no Redis index maps).
301
+
302
+ ## API surface (`streamgine.diff`)
303
+
304
+ | Symbol | Role |
305
+ | --------------------------- | ---------------------------------------------------------- |
306
+ | `configure()` | Load credentials once (JSON path or `DIFF_*` env) |
307
+ | `get_config()` | Read configured `ClientConfig` |
308
+ | `DiffClient` | `register()`, `register_test()`, `health()`, `get_state()` |
309
+ | `verify_webhook()` | Verify signature + return `WebhookEvent` |
310
+ | `DIFF_*` env name constants | `API_URL_ENV`, `API_KEY_ENV`, `SIGNING_SECRET_ENV`, … |
311
+ | `WebhookEvent` | Parsed `action`, optional `entity_id`, optional `after` |
312
+ | `WebhookVerificationError` | Invalid/missing signature or malformed payload |
313
+ | `SIGNATURE_HEADER` | `"X-Diff-Signature"` |
314
+
315
+ Direct imports also work: `from streamgine.diff import DiffClient`.
316
+
317
+ ## Development
318
+
319
+ ```bash
320
+ cd clients/python
321
+ python -m pip install -e ".[dev]"
322
+ pytest
323
+ ```
@@ -0,0 +1,69 @@
1
+ # Publishing the Python client to PyPI
2
+
3
+ Package: **`streamgine`** · import: **`from streamgine import diff`**
4
+
5
+ Only `clients/python/src/streamgine/` is published. Repo dev harnesses (load test, standalone agents) live in [`tools/python/`](../../tools/python/README.md) and are **not** included in the wheel.
6
+
7
+ ## Multi-product packaging (next products)
8
+
9
+ Today Diff is the only product, so one wheel named `streamgine` is fine.
10
+
11
+ When Streamgine adds another Python product (e.g. training-data):
12
+
13
+ 1. Publish **one distribution per product**: `streamgine-diff`, `streamgine-training`, …
14
+ 2. Keep imports under the **`streamgine`** namespace: `from streamgine import diff`, `from streamgine import training`
15
+ 3. Optionally publish a thin meta package `streamgine` with extras (`streamgine[diff]`, `streamgine[training]`, `streamgine[all]`)
16
+ 4. Target monorepo layout: `clients/python/packages/streamgine-diff/`, `…/streamgine-training/`, `…/streamgine-meta/`
17
+
18
+ Do **not** grow a single fat wheel that forces Diff customers to download unrelated products. Full rule: `.cursor/rules/python-client.mdc`.
19
+
20
+ ## One-time setup
21
+
22
+ 1. Create a [PyPI](https://pypi.org/) account and project (or use an existing one).
23
+ 2. Create a **trusted publisher** on PyPI for this repo:
24
+ - Owner: `scurtutech`
25
+ - Repository: `diff-events-service`
26
+ - Workflow: `publish-python.yml`
27
+ - Environment: `pypi` (optional but recommended)
28
+ 3. In GitHub → **Settings → Environments → pypi**, add protection rules if desired.
29
+
30
+ No long-lived API token is required when using [OIDC trusted publishing](https://docs.pypi.org/trusted-publishers/).
31
+
32
+ ## Release
33
+
34
+ 1. Bump `version` in `clients/python/pyproject.toml` and `clients/python/src/streamgine/__init__.py`.
35
+ 2. Merge to `main`.
36
+ 3. Create a GitHub **Release** (tag e.g. `python-v1.0.1`). Publishing runs automatically on `release: published`.
37
+
38
+ ## Manual / dry run
39
+
40
+ **Actions → Publish Python client → Run workflow**
41
+
42
+ - Default **dry run** builds the wheel/sdist and runs tests only.
43
+ - Uncheck dry run to upload (requires trusted publisher configured).
44
+
45
+ ## Local verify
46
+
47
+ ```bash
48
+ cd clients/python
49
+ python -m pip install build
50
+ python -m build
51
+ pip install dist/streamgine-*.whl
52
+ pytest
53
+ ```
54
+
55
+ ## Naming (do not rename on publish)
56
+
57
+ Wire identifiers stay as-is per product branding:
58
+
59
+ | Keep | Example |
60
+ | -------------------- | ---------------------------------------------------------------- |
61
+ | PyPI / pip name | `streamgine` (today = Diff only; later meta / `streamgine-diff`) |
62
+ | Import module | `streamgine.diff` (`from streamgine import diff`) |
63
+ | Client class | `DiffClient` |
64
+ | Env vars | `DIFF_*` via `configure()` |
65
+ | Signature header | `X-Diff-Signature` |
66
+ | Credentials filename | `diff-streamgine-credentials.json` |
67
+ | Env script filename | `diff-streamgine-env.sh` |
68
+
69
+ Customer-facing prose uses **Diff by Streamgine** or **Diff**, never **Diff Events**.