restless-stream 0.1.1__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.
Binary file
@@ -0,0 +1,16 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ pnpm-publish-summary.json
5
+ .turbo/
6
+ .DS_Store
7
+
8
+ .venv/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ __pycache__/
13
+ *.egg-info/
14
+ packages/python/**/dist/
15
+ packages/python/**/build/
16
+ temp
@@ -0,0 +1,412 @@
1
+ Metadata-Version: 2.4
2
+ Name: restless-stream
3
+ Version: 0.1.1
4
+ Summary: Python SDK for Restless Stream
5
+ Project-URL: Homepage, https://github.com/restless-stream/sdk/tree/main/packages/python/core#readme
6
+ Project-URL: Repository, https://github.com/restless-stream/sdk
7
+ Project-URL: Issues, https://github.com/restless-stream/sdk/issues
8
+ Author: Restless Stream
9
+ License: MIT
10
+ Keywords: restless-stream,sdk,sse,streaming,websocket
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.14
23
+ Requires-Dist: httpx-sse>=0.4
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: pydantic>=2
26
+ Requires-Dist: typing-extensions>=4.8
27
+ Requires-Dist: websockets>=16.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
32
+ Requires-Dist: pytest>=9.0.3; extra == 'dev'
33
+ Requires-Dist: ruff>=0.15.16; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # restless-stream
37
+
38
+ Official Python SDK for Restless Stream: <https://restlessapi.stream>
39
+
40
+ Restless Stream turns REST APIs into live Server-Sent Events and WebSocket streams. This package provides synchronous REST and SSE support, asynchronous REST, SSE, and WebSocket support, typed Pydantic models, runtime URL builders, and HMAC signature helpers.
41
+
42
+ ## Requirements
43
+
44
+ - Python `>=3.9`.
45
+ - A Restless Stream account and API key.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ python -m pip install restless-stream
51
+ ```
52
+
53
+ For local development from this repository:
54
+
55
+ ```bash
56
+ python -m pip install -e "packages/python/core[dev]"
57
+ ```
58
+
59
+ ## Getting Started
60
+
61
+ ### Async Client
62
+
63
+ Use `AsyncRestlessStreamClient` when you need async REST calls, SSE subscriptions, or WebSocket subscriptions.
64
+
65
+ ```python
66
+ import asyncio
67
+ import os
68
+
69
+ from restless_stream import AsyncRestlessStreamClient
70
+
71
+
72
+ async def main() -> None:
73
+ async with AsyncRestlessStreamClient(api_key=os.environ["RESTLESS_API_KEY"]) as client:
74
+ stream = await client.streams.create(
75
+ name="Orders",
76
+ description="Live order feed",
77
+ status="ACTIVE",
78
+ method="GET",
79
+ url="https://api.example.com/orders",
80
+ payload_mode="FULL_DATA",
81
+ polling_interval=30,
82
+ )
83
+
84
+ async for event in client.streams.subscribe_sse(stream.sse_url, reconnect=False):
85
+ print(event.type, event.data)
86
+
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ### Sync Client
92
+
93
+ Use `RestlessStreamClient` for synchronous REST calls and SSE subscriptions.
94
+
95
+ ```python
96
+ import os
97
+
98
+ from restless_stream import RestlessStreamClient
99
+
100
+
101
+ with RestlessStreamClient(api_key=os.environ["RESTLESS_API_KEY"]) as client:
102
+ streams = client.streams.list(limit=20, offset=0)
103
+
104
+ for stream in streams.streams:
105
+ print(stream.id, stream.name)
106
+ ```
107
+
108
+ ## Client Configuration
109
+
110
+ ```python
111
+ from restless_stream import AsyncRestlessStreamClient, RestlessStreamClient
112
+
113
+ client = RestlessStreamClient(
114
+ api_key="rs_...",
115
+ base_url="https://api.restlessapi.stream",
116
+ stream_base_url="https://stream.restlessapi.stream",
117
+ timeout=30.0,
118
+ )
119
+
120
+ async_client = AsyncRestlessStreamClient(
121
+ api_key="rs_...",
122
+ timeout=30.0,
123
+ )
124
+ ```
125
+
126
+ | Option | Description |
127
+ | --- | --- |
128
+ | `api_key` | Sends `x-api-key` on REST requests and `Authorization: Bearer <key>` on stream runtime requests. |
129
+ | `base_url` | REST API base URL. Defaults to `https://api.restlessapi.stream`. |
130
+ | `stream_base_url` | Runtime stream base URL. Defaults to `https://stream.restlessapi.stream`. |
131
+ | `timeout` | HTTP timeout in seconds for the owned `httpx` client. Defaults to `30.0`. |
132
+ | `http_client` | Optional `httpx.Client` or `httpx.AsyncClient`. When provided, the SDK does not close it. |
133
+
134
+ Both clients support context managers. Use `close()` for the sync client and `await aclose()` for the async client when you do not use a context manager.
135
+
136
+ When creating or updating streams, the SDK adds `apiKey` to the request body when the client has an API key and the body does not already include `apiKey` or `api_key`.
137
+
138
+ ## Stream Management
139
+
140
+ All stream management methods are available as top-level client methods and through `client.streams`.
141
+
142
+ ```python
143
+ stream = await client.streams.create(
144
+ name="Orders",
145
+ description="Live order feed",
146
+ status="ACTIVE",
147
+ method="GET",
148
+ url="https://api.example.com/orders",
149
+ headers={"Accept": "application/json"},
150
+ payload_mode="FULL_DATA",
151
+ polling_interval=30,
152
+ )
153
+
154
+ await client.streams.update(stream.id, name="Orders v2", polling_interval=60)
155
+ await client.streams.stop(stream.id)
156
+ await client.streams.start(stream.id)
157
+
158
+ usage = await client.streams.credit_usage_stats(stream.id)
159
+ snippets = await client.streams.connection_snippets(stream_id=stream.id, language="python")
160
+ ```
161
+
162
+ | Top-level method | Resource method | Description |
163
+ | --- | --- | --- |
164
+ | `list_streams(limit=20, offset=0)` | `streams.list(...)` | List streams. |
165
+ | `get_stream(stream_id)` | `streams.get(stream_id)` | Get one stream. |
166
+ | `create_stream(data=None, **kwargs)` | `streams.create(...)` | Create a persisted stream. |
167
+ | `update_stream(stream_id, data=None, **kwargs)` | `streams.update(...)` | Patch stream configuration. |
168
+ | `start_stream(stream_id)` | `streams.start(stream_id)` | Mark a stream active. |
169
+ | `stop_stream(stream_id)` | `streams.stop(stream_id)` | Mark a stream inactive. |
170
+ | `delete_stream(stream_id)` | `streams.delete(stream_id)` | Delete a stream. |
171
+ | `validate_stream_api_key(api_key=None)` | `streams.validate_api_key(...)` | Validate an API key. Uses the client key when omitted. |
172
+ | `credit_usage_stats(stream_id)` | `streams.credit_usage_stats(...)` | Fetch credit usage totals and daily usage. |
173
+ | `connection_snippets(data=None, **kwargs)` | `streams.connection_snippets(...)` | Generate runtime URLs and snippets. |
174
+ | `direct_setup(data=None, **kwargs)` | `streams.direct_setup(...)` | Generate direct-stream commands, URLs, and snippets. |
175
+ | `direct_session(data=None, **kwargs)` | `streams.direct_session(...)` | Create or reuse a direct stream session. |
176
+ | `subscribe_sse(url, **kwargs)` | `streams.subscribe_sse(...)` | Subscribe to an SSE runtime URL. |
177
+ | `subscribe_direct_sse(**kwargs)` | `streams.subscribe_direct_sse(...)` | Build and subscribe to a direct SSE URL. |
178
+ | `subscribe_websocket(url, **kwargs)` | `streams.subscribe_websocket(...)` | Async client only. Subscribe to a WebSocket runtime URL. |
179
+ | `subscribe_direct_websocket(**kwargs)` | `streams.subscribe_direct_websocket(...)` | Async client only. Build and subscribe to a direct WebSocket URL. |
180
+
181
+ Request bodies may be mappings, Pydantic models, or keyword arguments. Snake-case keys are converted to the camel-case field names expected by the API.
182
+
183
+ ```python
184
+ await client.streams.create(
185
+ name="Orders",
186
+ description="",
187
+ status="ACTIVE",
188
+ method="POST",
189
+ url="https://api.example.com/orders/search",
190
+ body={"status": "open"},
191
+ payload_mode="JSON_PATCH",
192
+ polling_interval=30,
193
+ )
194
+ ```
195
+
196
+ ## Direct Streams
197
+
198
+ Direct streams let you stream a REST endpoint without creating a persisted stream first.
199
+
200
+ ```python
201
+ async for event in client.streams.subscribe_direct_sse(
202
+ url="https://api.example.com/orders",
203
+ method="GET",
204
+ polling_interval=30,
205
+ reconnect=False,
206
+ ):
207
+ print(event)
208
+ ```
209
+
210
+ Create a reusable direct session when you need a stable URL.
211
+
212
+ ```python
213
+ session = await client.streams.direct_session(
214
+ dedupe_key="orders-feed-v1",
215
+ method="GET",
216
+ url="https://api.example.com/orders",
217
+ polling_interval=30,
218
+ )
219
+
220
+ async for event in client.streams.subscribe_sse(session.sse_url):
221
+ print(event.data)
222
+ ```
223
+
224
+ Generate setup commands and code snippets without subscribing.
225
+
226
+ ```python
227
+ setup = await client.streams.direct_setup(
228
+ method="POST",
229
+ url="https://api.example.com/search",
230
+ body={"query": "restless"},
231
+ language="python",
232
+ )
233
+
234
+ print(setup.commands.header.curl)
235
+ print(setup.runtime.direct_sse_url)
236
+ ```
237
+
238
+ Direct stream inputs support `url`, `method`, `headers`, `body`, `jq_filter`, `payload_mode`, `polling_interval`, `polling_strategies`, and `api_key`.
239
+
240
+ ## SSE Streaming
241
+
242
+ SSE subscriptions yield `StreamEvent` Pydantic models.
243
+
244
+ ```python
245
+ for event in sync_client.streams.subscribe_sse(
246
+ "https://stream.restlessapi.stream?streamId=stream_123",
247
+ cursor="1700000000000",
248
+ reconnect=False,
249
+ ):
250
+ print(event.type, event.data)
251
+ ```
252
+
253
+ ```python
254
+ async for event in async_client.streams.subscribe_sse(
255
+ "https://stream.restlessapi.stream?streamId=stream_123",
256
+ since="2026-01-01T00:00:00Z",
257
+ max_reconnects=3,
258
+ retry_seconds=2,
259
+ ):
260
+ print(event.type, event.meta.timestamp)
261
+ ```
262
+
263
+ SSE options:
264
+
265
+ | Option | Description |
266
+ | --- | --- |
267
+ | `cursor` | Initial cursor or SSE event ID. Updated automatically from received events. |
268
+ | `since` | Initial timestamp or cursor used until an event cursor is observed. |
269
+ | `reconnect` | Reconnect after the stream ends or errors. Defaults to `True`. |
270
+ | `max_reconnects` | Maximum reconnect attempts. `None` means unbounded. |
271
+ | `retry_seconds` | Delay between reconnect attempts. Defaults to `1.0`. |
272
+
273
+ The SDK sends `Authorization: Bearer <api_key>` for runtime subscriptions when the client has an API key.
274
+
275
+ ## WebSocket Streaming
276
+
277
+ WebSocket subscriptions are async-only.
278
+
279
+ ```python
280
+ async for event in async_client.streams.subscribe_websocket(
281
+ "wss://stream.restlessapi.stream/ws?streamId=stream_123",
282
+ cursor="42",
283
+ reconnect=True,
284
+ max_reconnects=3,
285
+ ):
286
+ print(event.type, event.data)
287
+ ```
288
+
289
+ Direct WebSocket subscription:
290
+
291
+ ```python
292
+ async for event in async_client.streams.subscribe_direct_websocket(
293
+ url="https://api.example.com/orders",
294
+ method="GET",
295
+ polling_interval=30,
296
+ connect_kwargs={"open_timeout": 10},
297
+ ):
298
+ print(event)
299
+ ```
300
+
301
+ WebSocket options include all SSE runtime options plus `connect_kwargs`, which are passed to `websockets.connect`. Do not include `additional_headers` or `extra_headers` in `connect_kwargs` when the SDK client has an `api_key`; the SDK owns the runtime Authorization header in that case.
302
+
303
+ ## Runtime URL Helpers
304
+
305
+ Build runtime URLs without creating a client.
306
+
307
+ ```python
308
+ from restless_stream import (
309
+ build_direct_sse_url,
310
+ build_direct_websocket_url,
311
+ build_sse_url,
312
+ build_websocket_url,
313
+ to_websocket_url,
314
+ )
315
+
316
+ sse_url = build_sse_url(stream_id="stream_123", since="2026-01-01T00:00:00Z")
317
+ ws_url = build_websocket_url(session_id="session_123")
318
+
319
+ direct_sse_url = build_direct_sse_url(
320
+ url="https://api.example.com/orders",
321
+ method="POST",
322
+ body={"status": "open"},
323
+ payload_mode="JSON_PATCH",
324
+ polling_interval=30,
325
+ )
326
+
327
+ direct_ws_url = build_direct_websocket_url(url="https://api.example.com/orders")
328
+ converted_ws_url = to_websocket_url(sse_url)
329
+ ```
330
+
331
+ `build_sse_url` requires exactly one of `stream_id` or `session_id`.
332
+
333
+ ## Models
334
+
335
+ Response models are Pydantic v2 models. Python attributes use snake case and accept the API's camel-case aliases.
336
+
337
+ Common models and enums:
338
+
339
+ | Export | Description |
340
+ | --- | --- |
341
+ | `RestlessModel` | Base Pydantic model with camel-case aliases and extra fields allowed. |
342
+ | `HttpMethod` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`. |
343
+ | `StreamStatus` | `ACTIVE` or `INACTIVE`. |
344
+ | `PayloadMode` | `FULL_DATA` or `JSON_PATCH`. |
345
+ | `PollingStrategy` | Time-windowed polling strategy. |
346
+ | `Stream` | Persisted stream response. |
347
+ | `StreamsResponse` | Stream list response with pagination info. |
348
+ | `BaseActionResponse` | Generic action response. |
349
+ | `StreamCreditUsageStats`, `DailyCreditUsage`, `CreditUsageChargeTypeBreakdown` | Credit usage responses. |
350
+ | `ConnectionSnippetsResponse` | Managed stream runtime URLs and snippets. |
351
+ | `DirectSetupResponse` | Direct setup commands, runtime URLs, snippets, and stream config. |
352
+ | `DirectSessionResponse` | Direct session runtime URLs and expiry. |
353
+ | `StreamEvent`, `StreamEventMeta`, `StreamErrorDetail` | Runtime event models. |
354
+
355
+ Runtime update events contain `type`, `meta`, `data`, optional `signature`, and optional `event_id`. Error events contain `type`, `meta`, and `error`.
356
+
357
+ ## Error Handling
358
+
359
+ REST API failures raise `RestlessStreamAPIError`.
360
+
361
+ ```python
362
+ from restless_stream import RestlessStreamAPIError
363
+
364
+ try:
365
+ stream = client.streams.get("missing")
366
+ except RestlessStreamAPIError as error:
367
+ print(error.status_code, error.message)
368
+ ```
369
+
370
+ Invalid runtime event payloads raise `RestlessStreamParseError`. Both exceptions inherit from `RestlessStreamError`.
371
+
372
+ ## HMAC Helpers
373
+
374
+ Use HMAC helpers to compute or verify Restless Stream HMAC-SHA256 signatures over JSON-compatible payloads.
375
+
376
+ ```python
377
+ from restless_stream import compute_hmac_signature, verify_hmac_signature
378
+
379
+ payload = {"id": "order_123", "total": 42}
380
+ signature = compute_hmac_signature("secret", payload)
381
+
382
+ if verify_hmac_signature("secret", payload, signature):
383
+ print("valid")
384
+ ```
385
+
386
+ When verifying a runtime event signature, pass the same JSON payload your integration signs and the event's `signature` value.
387
+
388
+ ## Public Exports
389
+
390
+ | Export | Purpose |
391
+ | --- | --- |
392
+ | `RestlessStreamClient` | Synchronous REST and SSE client. |
393
+ | `AsyncRestlessStreamClient` | Asynchronous REST, SSE, and WebSocket client. |
394
+ | `RestlessStreamError` | Base SDK exception. |
395
+ | `RestlessStreamAPIError` | REST API error exception. |
396
+ | `RestlessStreamParseError` | Runtime event parsing exception. |
397
+ | `compute_hmac_signature` | Computes an HMAC-SHA256 signature for a JSON-compatible payload. |
398
+ | `verify_hmac_signature` | Constant-time HMAC signature verification. |
399
+ | `DEFAULT_STREAM_BASE_URL` | Default runtime stream base URL. |
400
+ | `build_sse_url`, `build_websocket_url` | Managed stream runtime URL builders. |
401
+ | `build_direct_sse_url`, `build_direct_websocket_url` | Direct stream runtime URL builders. |
402
+ | `to_websocket_url` | Converts an SSE URL to a WebSocket URL. |
403
+ | Models and enums | Pydantic response models and enum types listed above. |
404
+
405
+ ## Development
406
+
407
+ ```bash
408
+ python -m pip install -e "packages/python/core[dev]"
409
+ python -m ruff check packages/python/core packages/python/examples
410
+ cd packages/python/core
411
+ python -m pytest tests
412
+ ```