Skip to content

Commit 4d335dd

Browse files
yokoflyclaude
andcommitted
docs: add dedicated Python External Stream page (#627)
Split the detailed Python External Stream content out of the SQL reference into docs/python-external-stream.md, matching the Kafka / Pulsar / NATS JetStream pattern. The SQL reference keeps a compact syntax block, version note, and link out. Sidebar entries added under CONNECT DATA IN and SEND DATA OUT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5b1485d commit 4d335dd

5 files changed

Lines changed: 145 additions & 1 deletion

File tree

docs/external-stream.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ You can create **External Streams** in Timeplus to query data in the external sy
44

55
You can run streaming analytics with the external streams in the similar way as other streams.
66

7-
Timeplus supports 5 types of external streams:
7+
Timeplus supports 6 types of external streams:
88
* [Kafka External Stream](/kafka-source)
99
* [Pulsar External Stream](/pulsar-source)
1010
* [NATS JetStream External Stream](/nats-jetstream-source)
11+
* [Python External Stream](/python-external-stream), only available in Timeplus Enterprise
1112
* [Timeplus External Stream](/timeplus-source), only available in Timeplus Enterprise
1213
* [Log External Stream](/log-stream) (experimental)
1314

docs/python-external-stream.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
id: python-external-stream
3+
title: Python External Stream
4+
---
5+
6+
# Python External Stream
7+
8+
Python External Stream lets you read from and write to arbitrary sources by embedding a Python body directly in the DDL. It is available in **Timeplus Enterprise 3.1.1+**.
9+
10+
Unlike the Kafka, Pulsar, and NATS JetStream external streams — which speak a specific wire protocol — a Python External Stream is a generic escape hatch: you bring the protocol, the client library, and the logic. Timeplus calls your functions inside the embedded CPython runtime and treats return values as row batches. The same DDL object can serve as both a source (via `read_function_name`) and a sink (via `write_function_name`).
11+
12+
## Minimum version matrix
13+
14+
| Feature | Minimum Timeplus Enterprise version |
15+
|---|---|
16+
| `type='python'` external stream (read + write) | 3.1.1 |
17+
| `init_function_name`, `deinit_function_name`, `init_function_parameters` lifecycle hooks | 3.2.1 |
18+
| `__timeplus_local_api_user` / `__timeplus_local_api_password` injected globals | 3.2.2 |
19+
20+
## Syntax
21+
22+
```sql
23+
CREATE EXTERNAL STREAM [IF NOT EXISTS] stream_name (<col_name1> <col_type>)
24+
AS $$
25+
def read_fn():
26+
...
27+
28+
def write_fn(col1, ...):
29+
...
30+
31+
def init_fn(config): # optional, 3.2.1+
32+
...
33+
34+
def deinit_fn(): # optional, 3.2.1+
35+
...
36+
$$
37+
SETTINGS
38+
type = 'python', -- required
39+
read_function_name = '..', -- defaults to the stream name
40+
write_function_name = '..', -- defaults to read_function_name
41+
init_function_name = '..', -- 3.2.1+
42+
init_function_parameters = '..', -- 3.2.1+ (requires init_function_name)
43+
deinit_function_name = '..', -- 3.2.1+
44+
mode = 'auto' -- 'auto' (default), 'streaming', or 'batch'
45+
```
46+
47+
## Settings
48+
49+
* **type**: must be `'python'`. Required.
50+
* **read_function_name**: name of the Python function used when the stream is read from. Defaults to the stream name.
51+
* **write_function_name**: name of the Python function used when the stream is written to (sink). Defaults to `read_function_name`.
52+
* **init_function_name** *(Timeplus Enterprise 3.2.1+)*: name of a Python function called once before read/write processing begins. Use it to open connections, warm caches, or stash state on `builtins` for the entry function to consume.
53+
* **init_function_parameters** *(Timeplus Enterprise 3.2.1+)*: a single string passed as the only argument to the init function. Any format works (JSON, `key=value`, or a plain string) — parsing is up to your Python code. Requires `init_function_name`; otherwise the stream fails to create with `Setting 'init_function_parameters' requires 'init_function_name' to be configured`.
54+
* **deinit_function_name** *(Timeplus Enterprise 3.2.1+)*: name of a Python function called once after read/write processing completes, for cleanup.
55+
* **mode**: Python table execution mode — `'auto'` (default), `'streaming'`, or `'batch'`.
56+
57+
:::info
58+
Attempting to use `init_function_name`, `deinit_function_name`, or `init_function_parameters` on versions earlier than 3.2.1 fails with:
59+
```
60+
Code: 115. DB::Exception: Unknown setting init_function_name: for storage ExternalStream.
61+
```
62+
Upgrade to 3.2.1 or later to use these hooks.
63+
:::
64+
65+
## Local API credentials *(Timeplus Enterprise 3.2.2+)*
66+
67+
When the local API user is enabled on the server, Timeplus injects two module-level globals into every Python External Stream module so your code can authenticate back to the same timeplusd over the native TCP protocol or the REST HTTP interface without hard-coding credentials:
68+
69+
* `__timeplus_local_api_user` — the ephemeral local API username.
70+
* `__timeplus_local_api_password` — the matching token. Treat this as a secret; do not log it.
71+
72+
Both globals are available as bare names inside the Python body — no `os.environ` lookup needed. They are regenerated on every server restart and never written to disk.
73+
74+
## Example: init / deinit hooks
75+
76+
```sql
77+
CREATE EXTERNAL STREAM py_cookie_counter
78+
(
79+
previous_cleanup_count int32,
80+
secret_flavor string
81+
)
82+
AS $$
83+
import builtins, json
84+
85+
def open_bakery(config):
86+
builtins._tp_cookie_secret_flavor = json.loads(config)["flavor"]
87+
88+
def close_bakery():
89+
if hasattr(builtins, "_tp_cookie_secret_flavor"):
90+
del builtins._tp_cookie_secret_flavor
91+
92+
def serve_cookie_report():
93+
return [(0, getattr(builtins, "_tp_cookie_secret_flavor", ""))]
94+
$$
95+
SETTINGS
96+
type = 'python',
97+
read_function_name = 'serve_cookie_report',
98+
init_function_name = 'open_bakery',
99+
init_function_parameters = '{"flavor":"double-chocolate"}',
100+
deinit_function_name = 'close_bakery';
101+
```
102+
103+
## Related
104+
105+
- [Python UDF](/py-udf) — the embedded Python runtime, library management, and data-type mapping shared with Python External Stream.
106+
- [CREATE EXTERNAL STREAM](/sql-create-external-stream) — SQL reference for all external stream types.

docs/sql-create-external-stream.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,28 @@ Please check the [Pulsar External Stream](/pulsar-source) for more details.
5555

5656
Please check the [NATS JetStream External Stream](/nats-jetstream-source) for more details.
5757

58+
## Python External Stream
59+
60+
```sql
61+
CREATE EXTERNAL STREAM [IF NOT EXISTS] stream_name (<col_name1> <col_type>)
62+
AS $$
63+
def read_fn():
64+
...
65+
$$
66+
SETTINGS
67+
type = 'python', -- required
68+
read_function_name = '..',
69+
write_function_name = '..',
70+
init_function_name = '..', -- 3.2.1+
71+
init_function_parameters = '..', -- 3.2.1+
72+
deinit_function_name = '..', -- 3.2.1+
73+
mode = 'auto' -- 'auto' (default), 'streaming', or 'batch'
74+
```
75+
76+
Available in **Timeplus Enterprise 3.1.1+**. Lifecycle hooks (`init_function_name`, `deinit_function_name`, `init_function_parameters`) require **3.2.1+**, and injected local-API credentials require **3.2.2+**.
77+
78+
Please check the [Python External Stream](/python-external-stream) for more details about settings, lifecycle hooks, and examples.
79+
5880
## Timeplus External Stream
5981
```sql
6082
CREATE EXTERNAL STREAM [IF NOT EXISTS] stream_name (<col_name1> <col_type>)

sidebars.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ const sidebars = {
165165
label: "PostgreSQL",
166166
id: "pg-external-table",
167167
},
168+
{
169+
type: "doc",
170+
label: "Python",
171+
id: "python-external-stream",
172+
},
168173
{
169174
type: "doc",
170175
label: "S3",
@@ -545,6 +550,11 @@ const sidebars = {
545550
label: "NATS JetStream",
546551
id: "nats-jetstream-sink",
547552
},
553+
{
554+
type: "doc",
555+
label: "Python",
556+
id: "python-external-stream",
557+
},
548558
{
549559
type: "doc",
550560
label: "S3",

tools/spellchecker/dic.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,8 @@ DefaultTableEngine
886886
defaultValue
887887
defaultValueOfArgumentType
888888
defaultValueOfTypeName
889+
deinit
890+
deinit_function_name
889891
DelayedInserts
890892
DELETEs
891893
delim
@@ -1316,6 +1318,7 @@ globalNotIn
13161318
GlobalThread
13171319
GlobalThreadActive
13181320
globalVariable
1321+
globals
13191322
globbing
13201323
glushkovds
13211324
gofakeit
@@ -1526,6 +1529,8 @@ infty
15261529
Ingestions
15271530
ingressClassName
15281531
init
1532+
init_function_name
1533+
init_function_parameters
15291534
init_sql
15301535
initcap
15311536
initcapUTF

0 commit comments

Comments
 (0)