This is an automated email from the ASF dual-hosted git repository.
davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 2bed110610 [Feature][Client] Python SDK Client for SeaTunnel REST API
(#10184)
2bed110610 is described below
commit 2bed11061041df4b93b930b51f1719f0baa18f26
Author: Lucio Jr. <[email protected]>
AuthorDate: Tue May 26 11:02:59 2026 -0300
[Feature][Client] Python SDK Client for SeaTunnel REST API (#10184)
Co-authored-by: Daniel <[email protected]>
---
docs/en/engines/zeta/python-sdk.md | 115 +++++++++++++++
docs/sidebars.js | 3 +-
docs/zh/engines/zeta/python-sdk.md | 116 +++++++++++++++
tools/seatunnel-python-sdk/.gitignore | 3 +
tools/seatunnel-python-sdk/README.md | 53 +++++++
tools/seatunnel-python-sdk/requirements.txt | 4 +
tools/seatunnel-python-sdk/seatunnel/__init__.py | 31 ++++
tools/seatunnel-python-sdk/seatunnel/client.py | 91 ++++++++++++
.../seatunnel/endpoints/__init__.py | 14 ++
.../seatunnel/endpoints/cluster.py | 52 +++++++
.../seatunnel/endpoints/config.py | 32 +++++
.../seatunnel/endpoints/jobs.py | 91 ++++++++++++
.../seatunnel/endpoints/system.py | 28 ++++
.../seatunnel/helpers/__init__.py | 14 ++
.../seatunnel/helpers/httpMethod.py | 20 +++
.../seatunnel/helpers/jobStatus.py | 24 ++++
.../seatunnel/helpers/queryParams.py | 38 +++++
tools/seatunnel-python-sdk/setup.py | 27 ++++
tools/seatunnel-python-sdk/tests/test_sdk.py | 158 +++++++++++++++++++++
19 files changed, 913 insertions(+), 1 deletion(-)
diff --git a/docs/en/engines/zeta/python-sdk.md
b/docs/en/engines/zeta/python-sdk.md
new file mode 100644
index 0000000000..a95a9e2412
--- /dev/null
+++ b/docs/en/engines/zeta/python-sdk.md
@@ -0,0 +1,115 @@
+# SeaTunnel Python SDK
+
+The SeaTunnel Python SDK allows developers to interact with the SeaTunnel
Engine using Python. It provides wrappers around the SeaTunnel REST API for job
submission, management, and cluster monitoring.
+
+## Installation
+
+The SeaTunnel Python SDK is not published to PyPI yet. Install it from the
SeaTunnel source tree:
+
+```bash
+git clone https://github.com/apache/seatunnel.git
+cd seatunnel
+python -m pip install ./tools/seatunnel-python-sdk
+```
+
+Python 3.9 or newer is required.
+
+## Usage
+
+### Job Management
+
+You can use the `jobs` property of the client to manage jobs.
+
+#### Submit a Job
+
+```python
+from seatunnel import SeaTunnelClient, SubmitJobQueryParams
+
+config = """
+env {
+ job.mode = "batch"
+}
+source {
+ FakeSource {
+ plugin_output = "fake"
+ row.num = 100
+ schema = {
+ fields {
+ name = "string"
+ age = "int"
+ }
+ }
+ }
+}
+transform {}
+sink {
+ Console {}
+}
+"""
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ query_params = SubmitJobQueryParams()
+ response = client.jobs.submit_job(conf=config, params=query_params)
+ print(response)
+```
+
+#### Get Running Jobs
+
+```python
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ running_jobs = client.jobs.get_running_jobs()
+ print(running_jobs)
+```
+
+#### Get Job Details
+
+```python
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ job_id = 12345 # Replace with actual Job ID
+ job_details = client.jobs.get_job_details(jobId=job_id)
+ print(job_details)
+```
+
+### Cluster Monitoring
+
+You can use the `cluster` property to get cluster information.
+
+#### Get Cluster Overview
+
+```python
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ overview = client.cluster.get_overview({"jobId": "12345"})
+ print(overview)
+```
+
+## API Reference
+
+### Client
+
+**`SeaTunnelClient(base_url: str)`**
+
+- `base_url`: The address of the SeaTunnel Engine (e.g.,
`http://127.0.0.1:8080`).
+
+### Jobs (`client.jobs`)
+
+- **`submit_job(conf: str, params: SubmitJobQueryParams)`**: Submit a job with
a configuration string.
+- **`submit_job_from_file(filePath: str, params: SubmitJobFileQueryParams)`**:
Submit a job using a configuration file path.
+- **`submit_jobs(confs: str)`**: Submit multiple jobs in batch.
+- **`stop_job(params: StopJobQueryParams)`**: Stop a specific job.
+- **`stop_jobs(params: list[StopJobQueryParams])`**: Stop multiple jobs.
+- **`get_running_jobs()`**: Retrieve a list of currently running jobs.
+- **`get_job_details(jobId: int)`**: Retrieve details for a specific job.
+- **`get_finished_jobs_info(state: Optional[JobStatus] = None)`**: Retrieve
information about finished jobs, optionally filtered by status.
+
+### Cluster (`client.cluster`)
+
+- **`get_overview(params: Optional[dict[str, str]] = None)`**: Get the cluster
overview with optional tag filters forwarded as query parameters.
+- **`get_metrics()`**: Get cluster metrics.
+- **`get_log()`**: Get logs from a single node.
+- **`get_logs(jobId: Optional[int] = None)`**: Get logs from all nodes,
optionally filtered by Job ID.
+
+### Helper Classes
+
+- **`SubmitJobQueryParams`**: Parameters for submitting a job.
+- **`SubmitJobFileQueryParams`**: Parameters for submitting a job from a file.
+- **`StopJobQueryParams`**: Parameters for stopping a job.
+- **`JobStatus`**: Enum for job status.
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 1a3c924001..cbc39ae7bd 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -298,7 +298,8 @@ const sidebars = {
"items": [
"engines/zeta/rest-api-v1",
"engines/zeta/rest-api-v2",
- "engines/zeta/security"
+ "engines/zeta/security",
+ "engines/zeta/python-sdk"
]
},
"engines/zeta/user-command",
diff --git a/docs/zh/engines/zeta/python-sdk.md
b/docs/zh/engines/zeta/python-sdk.md
new file mode 100644
index 0000000000..3d64c6891e
--- /dev/null
+++ b/docs/zh/engines/zeta/python-sdk.md
@@ -0,0 +1,116 @@
+# SeaTunnel Python SDK
+
+SeaTunnel Python SDK 允许开发者使用 Python 与 SeaTunnel Engine 进行交互。它提供了围绕 SeaTunnel
REST API 的封装,用于作业提交、管理和集群监控。
+
+## 安装
+
+SeaTunnel Python SDK 目前还没有发布到 PyPI,需要直接从 SeaTunnel 源码目录安装:
+
+```bash
+git clone https://github.com/apache/seatunnel.git
+cd seatunnel
+python -m pip install ./tools/seatunnel-python-sdk
+```
+
+需要 Python 3.9 或更高版本。
+
+## 使用方法
+
+### 作业管理
+
+您可以使用客户端的 `jobs` 属性来管理作业。
+
+#### 提交作业
+
+```python
+from seatunnel import SeaTunnelClient, SubmitJobQueryParams
+
+config = """
+env {
+ job.mode = "batch"
+}
+source {
+ FakeSource {
+ plugin_output = "fake"
+ row.num = 100
+ schema = {
+ fields {
+ name = "string"
+ age = "int"
+ }
+ }
+ }
+}
+transform {}
+sink {
+ Console {}
+}
+"""
+
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ query_params = SubmitJobQueryParams()
+ response = client.jobs.submit_job(conf=config, params=query_params)
+ print(response)
+```
+
+#### 获取运行中的作业
+
+```python
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ running_jobs = client.jobs.get_running_jobs()
+ print(running_jobs)
+```
+
+#### 获取作业详情
+
+```python
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ job_id = 12345 # 替换为实际的 Job ID
+ job_details = client.jobs.get_job_details(jobId=job_id)
+ print(job_details)
+```
+
+### 集群监控
+
+您可以使用 `cluster` 属性来获取集群信息。
+
+#### 获取集群概览
+
+```python
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ overview = client.cluster.get_overview({"jobId": "12345"})
+ print(overview)
+```
+
+## API 参考
+
+### Client (客户端)
+
+**`SeaTunnelClient(base_url: str)`**
+
+- `base_url`: SeaTunnel Engine 的地址 (例如:`http://127.0.0.1:8080`)。
+
+### Jobs (`client.jobs`)
+
+- **`submit_job(conf: str, params: SubmitJobQueryParams)`**: 使用配置字符串提交作业。
+- **`submit_job_from_file(filePath: str, params: SubmitJobFileQueryParams)`**:
使用配置文件路径提交作业。
+- **`submit_jobs(confs: str)`**: 批量提交多个作业。
+- **`stop_job(params: StopJobQueryParams)`**: 停止特定作业。
+- **`stop_jobs(params: list[StopJobQueryParams])`**: 停止多个作业。
+- **`get_running_jobs()`**: 获取当前正在运行的作业列表。
+- **`get_job_details(jobId: int)`**: 获取特定作业的详细信息。
+- **`get_finished_jobs_info(state: Optional[JobStatus] = None)`**:
获取已完成作业的信息,可按状态过滤。
+
+### Cluster (`client.cluster`)
+
+- **`get_overview(params: Optional[dict[str, str]] = None)`**:
获取集群概览,并将过滤标签作为查询参数透传给 REST API。
+- **`get_metrics()`**: 获取集群指标。
+- **`get_log()`**: 获取单个节点的日志。
+- **`get_logs(jobId: Optional[int] = None)`**: 获取所有节点的日志,可按 Job ID 过滤。
+
+### Helper Classes (辅助类)
+
+- **`SubmitJobQueryParams`**: 提交作业的参数。
+- **`SubmitJobFileQueryParams`**: 从文件提交作业的参数。
+- **`StopJobQueryParams`**: 停止作业的参数。
+- **`JobStatus`**: 作业状态枚举。
diff --git a/tools/seatunnel-python-sdk/.gitignore
b/tools/seatunnel-python-sdk/.gitignore
new file mode 100644
index 0000000000..9e56a48db7
--- /dev/null
+++ b/tools/seatunnel-python-sdk/.gitignore
@@ -0,0 +1,3 @@
+/build
+/dist
+*egg-info
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/README.md
b/tools/seatunnel-python-sdk/README.md
new file mode 100644
index 0000000000..b506e70f2f
--- /dev/null
+++ b/tools/seatunnel-python-sdk/README.md
@@ -0,0 +1,53 @@
+## Installation
+
+The SDK is not published to PyPI yet. Install it directly from the SeaTunnel
source tree:
+
+```bash
+git clone https://github.com/apache/seatunnel.git
+cd seatunnel
+python -m pip install ./tools/seatunnel-python-sdk
+```
+
+Python 3.9 or newer is required.
+
+## Usage
+
+With a server already running at port 8080, this example submits a job:
+
+``` py
+from seatunnel import SeaTunnelClient, SubmitJobQueryParams
+
+config = """
+env {
+ job.mode = "batch"
+}
+
+source {
+ FakeSource {
+ plugin_output = "fake"
+ row.num = 1000
+ schema = {
+ fields {
+ name = "string"
+ age = "int"
+ card = "int"
+ }
+ }
+ }
+}
+
+transform {
+}
+
+sink {
+ Console {
+ plugin_input = "fake"
+ }
+}
+"""
+
+with SeaTunnelClient(base_url="http://localhost:8080") as client:
+ query_params = SubmitJobQueryParams()
+ response = client.jobs.submit_job(conf=config, params=query_params)
+ print(response)
+```
diff --git a/tools/seatunnel-python-sdk/requirements.txt
b/tools/seatunnel-python-sdk/requirements.txt
new file mode 100644
index 0000000000..8eaf7e24f5
--- /dev/null
+++ b/tools/seatunnel-python-sdk/requirements.txt
@@ -0,0 +1,4 @@
+httpx
+setuptools
+wheel
+twine
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/__init__.py
b/tools/seatunnel-python-sdk/seatunnel/__init__.py
new file mode 100644
index 0000000000..73703306ba
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/__init__.py
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from .client import SeaTunnelClient
+from .helpers.jobStatus import JobStatus
+from .helpers.queryParams import (
+ SubmitJobQueryParams,
+ SubmitJobFileQueryParams,
+ StopJobQueryParams
+)
+
+__all__ = [
+ "SeaTunnelClient",
+ "JobStatus",
+ "SubmitJobQueryParams",
+ "SubmitJobFileQueryParams",
+ "StopJobQueryParams",
+]
diff --git a/tools/seatunnel-python-sdk/seatunnel/client.py
b/tools/seatunnel-python-sdk/seatunnel/client.py
new file mode 100644
index 0000000000..25d8fb551d
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/client.py
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import httpx
+from typing import Any
+
+from .helpers.httpMethod import HttpMethod
+
+
+class SeaTunnelError(Exception):
+ """Base exception for SeaTunnel SDK"""
+
+ pass
+
+
+class SeaTunnelAPIError(SeaTunnelError):
+ """API error (4xx/5xx responses)"""
+
+ def __init__(self, status_code: int, message: str):
+ self.status_code = status_code
+ self.message = message
+ super().__init__(f"[{status_code}] {message}")
+
+
+class SeaTunnelConnectionError(SeaTunnelError):
+ """Network connection error"""
+
+ pass
+
+
+class Client:
+ def __init__(self, base_url: str, timeout: float = 10):
+ self.base_url = base_url
+ self.timeout = timeout
+ self.session = httpx.Client(timeout=timeout)
+
+ def request(self, method: HttpMethod, path: str, **kwargs: Any):
+ """Send a REST request and normalize transport and API failures."""
+ try:
+ resp = self.session.request(method.value,
f"{self.base_url}{path}", **kwargs)
+ resp.raise_for_status()
+ except httpx.RequestError as exc:
+ raise SeaTunnelConnectionError(f"Failed to connect to
{exc.request.url}: {exc}") from exc
+ except httpx.HTTPStatusError as exc:
+ try:
+ error_data = exc.response.json()
+ message = error_data.get("message", exc.response.text)
+ except ValueError:
+ message = exc.response.text
+
+ raise SeaTunnelAPIError(status_code=exc.response.status_code,
message=message) from exc
+
+ content_type = resp.headers.get("Content-Type", "")
+ return resp.json() if "application/json" in content_type else resp.text
+
+ def close(self):
+ self.session.close()
+
+
+class SeaTunnelClient:
+ def __init__(self, base_url):
+ from .endpoints.config import ConfigApi
+ from .endpoints.cluster import ClusterApi
+ from .endpoints.jobs import JobsApi
+ from .endpoints.system import SystemApi
+
+ self.client = Client(base_url)
+
+ self.cluster = ClusterApi(self.client)
+ self.jobs = JobsApi(self.client)
+ self.config = ConfigApi(self.client)
+ self.system = SystemApi(self.client)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.client.close()
diff --git a/tools/seatunnel-python-sdk/seatunnel/endpoints/__init__.py
b/tools/seatunnel-python-sdk/seatunnel/endpoints/__init__.py
new file mode 100644
index 0000000000..a2f3e196c4
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/endpoints/__init__.py
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/endpoints/cluster.py
b/tools/seatunnel-python-sdk/seatunnel/endpoints/cluster.py
new file mode 100644
index 0000000000..2e18075e72
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/endpoints/cluster.py
@@ -0,0 +1,52 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from typing import Mapping, Optional
+
+from ..client import Client
+from ..helpers.httpMethod import HttpMethod
+
+
+class ClusterApi:
+ def __init__(self, client: Client):
+ self.client = client
+
+ def get_overview(self, params: Optional[Mapping[str, str]] = None):
+ """
+ Returns an overview over the Zeta engine cluster.
+ """
+ # The REST API accepts arbitrary tag key/value pairs as raw query
params.
+ return self.client.request(HttpMethod.GET, "/overview", params=params)
+
+ def get_metrics(self):
+ """
+ Get Node Metrics
+ """
+ return self.client.request(HttpMethod.GET, "/openmetrics")
+
+ def get_log(self):
+ """
+ Get Log Content from a Single Node
+ """
+ return self.client.request(HttpMethod.GET, "/log")
+
+ def get_logs(self, jobId: Optional[int] = None):
+ """
+ Get Logs from All Nodes
+ """
+ # The current REST servlet reads the job filter from `/logs/{jobId}`.
+ path = "/logs" if jobId is None else f"/logs/{jobId}"
+ return self.client.request(HttpMethod.GET, path, params={"format":
"json"})
diff --git a/tools/seatunnel-python-sdk/seatunnel/endpoints/config.py
b/tools/seatunnel-python-sdk/seatunnel/endpoints/config.py
new file mode 100644
index 0000000000..9b23812d98
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/endpoints/config.py
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from ..helpers.httpMethod import HttpMethod
+from ..client import Client
+
+class ConfigApi:
+ def __init__(self, client: Client):
+ self.client = client
+
+ def encrypt_config(self, conf: str):
+ """
+ Encrypt Config
+ """
+ return self.client.request(
+ HttpMethod.POST,
+ "/encrypt-config",
+ content=conf
+ )
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/endpoints/jobs.py
b/tools/seatunnel-python-sdk/seatunnel/endpoints/jobs.py
new file mode 100644
index 0000000000..ec1cdd435e
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/endpoints/jobs.py
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import json
+from typing import Optional
+
+from ..client import Client
+from ..helpers.httpMethod import HttpMethod
+from ..helpers.jobStatus import JobStatus
+from ..helpers.queryParams import (
+ StopJobQueryParams,
+ SubmitJobFileQueryParams,
+ SubmitJobQueryParams,
+)
+
+
+class JobsApi:
+ def __init__(self, client: Client):
+ self.client = client
+
+ def submit_job(self, conf: str, params: SubmitJobQueryParams):
+ """
+ Submit A Job
+ """
+ return self.client.request(
+ HttpMethod.POST, "/submit-job", content=conf,
params=params.__dict__
+ )
+
+ def submit_job_from_file(self, filePath: str, params:
SubmitJobFileQueryParams):
+ """
+ Submit A Job By Upload Config File
+ """
+ with open(filePath, "rb") as file:
+ return self.client.request(
+ HttpMethod.POST,
+ "/submit-job/upload",
+ files={"config_file": file},
+ params=params.__dict__,
+ )
+
+ def submit_jobs(self, confs: str):
+ """
+ Batch Submit Jobs
+ """
+ return self.client.request(HttpMethod.POST, "/submit-jobs",
content=confs)
+
+ def stop_job(self, params: StopJobQueryParams):
+ """
+ Stop A Job
+ """
+ return self.client.request(HttpMethod.POST, "/stop-job",
content=json.dumps(params.__dict__))
+
+ def stop_jobs(self, params: list[StopJobQueryParams]):
+ """
+ Batch Stop Jobs
+ """
+ return self.client.request(
+ HttpMethod.POST, "/stop-jobs", content=json.dumps([obj.__dict__
for obj in params])
+ )
+
+ def get_running_jobs(self):
+ """
+ Returns An Overview And State Of All Jobs
+ """
+ return self.client.request(HttpMethod.GET, "/running-jobs")
+
+ def get_job_details(self, jobId: int):
+ """
+ Return Details Of A Job
+ """
+ return self.client.request(HttpMethod.GET, f"/job-info/{jobId}")
+
+ def get_finished_jobs_info(self, state: Optional[JobStatus] = None):
+ """
+ Return Details Of A Job
+ """
+ jobStatus = "" if state is None else state.value
+ return self.client.request(HttpMethod.GET,
f"/finished-jobs/{jobStatus}")
diff --git a/tools/seatunnel-python-sdk/seatunnel/endpoints/system.py
b/tools/seatunnel-python-sdk/seatunnel/endpoints/system.py
new file mode 100644
index 0000000000..51bdd9fbd0
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/endpoints/system.py
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from ..helpers.httpMethod import HttpMethod
+from ..client import Client
+
+class SystemApi:
+ def __init__(self, client: Client):
+ self.client = client
+
+ def get_system_info(self):
+ """
+ Returns System Monitoring Information
+ """
+ return self.client.request(HttpMethod.GET,
"/system-monitoring-information")
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/helpers/__init__.py
b/tools/seatunnel-python-sdk/seatunnel/helpers/__init__.py
new file mode 100644
index 0000000000..a2f3e196c4
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/helpers/__init__.py
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/helpers/httpMethod.py
b/tools/seatunnel-python-sdk/seatunnel/helpers/httpMethod.py
new file mode 100644
index 0000000000..dfc28f0cd5
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/helpers/httpMethod.py
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import Enum
+
+class HttpMethod(Enum):
+ GET = "GET"
+ POST = "POST"
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/helpers/jobStatus.py
b/tools/seatunnel-python-sdk/seatunnel/helpers/jobStatus.py
new file mode 100644
index 0000000000..6fe9af5aca
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/helpers/jobStatus.py
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from enum import Enum
+
+class JobStatus(Enum):
+ FINISHED = "FINISHED"
+ CANCELED = "CANCELED"
+ FAILED = "FAILED"
+ SAVEPOINT_DONE = "SAVEPOINT_DONE"
+ UNKNOWABLE = "UNKNOWABLE"
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/seatunnel/helpers/queryParams.py
b/tools/seatunnel-python-sdk/seatunnel/helpers/queryParams.py
new file mode 100644
index 0000000000..afaf5c07de
--- /dev/null
+++ b/tools/seatunnel-python-sdk/seatunnel/helpers/queryParams.py
@@ -0,0 +1,38 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from dataclasses import dataclass
+from typing import Dict, Optional
+
+@dataclass
+class SubmitJobQueryParams:
+ jobId: Optional[int] = None
+ jobName: Optional[str] = None
+ isStartWithSavePoint: Optional[bool] = None
+ format: str = "hocon"
+
+@dataclass
+class SubmitJobFileQueryParams:
+ jobId: Optional[int] = None
+ jobName: Optional[str] = None
+ isStartWithSavePoint: Optional[bool] = None
+
+@dataclass
+class StopJobQueryParams:
+ jobId: int
+ isStartWithSavePoint: bool
+
+OverviewQueryParams = Dict[str, str]
diff --git a/tools/seatunnel-python-sdk/setup.py
b/tools/seatunnel-python-sdk/setup.py
new file mode 100644
index 0000000000..f7f2a00f8a
--- /dev/null
+++ b/tools/seatunnel-python-sdk/setup.py
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from setuptools import setup, find_packages
+
+setup(
+ name='seatunnel',
+ version='0.1',
+ packages=find_packages(),
+ install_requires=[
+ 'httpx'
+ ],
+ python_requires='>=3.9',
+)
\ No newline at end of file
diff --git a/tools/seatunnel-python-sdk/tests/test_sdk.py
b/tools/seatunnel-python-sdk/tests/test_sdk.py
new file mode 100644
index 0000000000..b9d9d6cc09
--- /dev/null
+++ b/tools/seatunnel-python-sdk/tests/test_sdk.py
@@ -0,0 +1,158 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+from unittest import mock
+
+import httpx
+
+from seatunnel import SeaTunnelClient, SubmitJobQueryParams
+from seatunnel.client import SeaTunnelAPIError, SeaTunnelConnectionError
+
+
+class RecordingSession:
+ def __init__(self, response=None, exc=None):
+ self.response = response
+ self.exc = exc
+ self.calls = []
+ self.closed = False
+
+ def request(self, method, url, **kwargs):
+ self.calls.append((method, url, kwargs))
+ if self.exc is not None:
+ raise self.exc
+ return self.response
+
+ def close(self):
+ self.closed = True
+
+
+class SeaTunnelSdkTest(unittest.TestCase):
+ def create_client(self, session):
+ with mock.patch("seatunnel.client.httpx.Client", return_value=session):
+ return SeaTunnelClient(base_url="http://localhost:8080")
+
+ def test_submit_job_sends_body_and_query_params(self):
+ response = httpx.Response(
+ 200,
+ json={"jobId": 1},
+ request=httpx.Request("POST", "http://localhost:8080/submit-job"),
+ )
+ session = RecordingSession(response=response)
+ sdk = self.create_client(session)
+
+ result = sdk.jobs.submit_job(
+ conf="env { job.mode = \"batch\" }",
+ params=SubmitJobQueryParams(jobName="demo-job"),
+ )
+
+ self.assertEqual({"jobId": 1}, result)
+ method, url, kwargs = session.calls[0]
+ self.assertEqual("POST", method)
+ self.assertEqual("http://localhost:8080/submit-job", url)
+ self.assertEqual("env { job.mode = \"batch\" }", kwargs["content"])
+ self.assertEqual("demo-job", kwargs["params"]["jobName"])
+ self.assertEqual("hocon", kwargs["params"]["format"])
+
+ def test_overview_forwards_filter_query_params(self):
+ response = httpx.Response(
+ 200,
+ json={"status": "ok"},
+ request=httpx.Request("GET", "http://localhost:8080/overview"),
+ )
+ session = RecordingSession(response=response)
+ sdk = self.create_client(session)
+
+ sdk.cluster.get_overview({"jobId": "12345", "pipelineId": "daily"})
+
+ _, url, kwargs = session.calls[0]
+ self.assertEqual("http://localhost:8080/overview", url)
+ self.assertEqual({"jobId": "12345", "pipelineId": "daily"},
kwargs["params"])
+
+ def test_get_logs_without_job_id_keeps_base_logs_endpoint(self):
+ response = httpx.Response(
+ 200,
+ json={"logs": []},
+ request=httpx.Request("GET", "http://localhost:8080/logs"),
+ )
+ session = RecordingSession(response=response)
+ sdk = self.create_client(session)
+
+ sdk.cluster.get_logs()
+
+ _, url, kwargs = session.calls[0]
+ self.assertEqual("http://localhost:8080/logs", url)
+ self.assertEqual({"format": "json"}, kwargs["params"])
+
+ def test_get_logs_with_job_id_uses_path_segment_contract(self):
+ response = httpx.Response(
+ 200,
+ json={"logs": []},
+ request=httpx.Request("GET",
"http://localhost:8080/logs/733584788375666689"),
+ )
+ session = RecordingSession(response=response)
+ sdk = self.create_client(session)
+
+ sdk.cluster.get_logs(jobId=733584788375666689)
+
+ _, url, kwargs = session.calls[0]
+ self.assertEqual("http://localhost:8080/logs/733584788375666689", url)
+ self.assertEqual({"format": "json"}, kwargs["params"])
+
+ def test_http_status_error_uses_response_message_and_preserves_cause(self):
+ response = httpx.Response(
+ 400,
+ json={"message": "job submission failed"},
+ request=httpx.Request("POST", "http://localhost:8080/submit-job"),
+ )
+ session = RecordingSession(response=response)
+ sdk = self.create_client(session)
+
+ with self.assertRaises(SeaTunnelAPIError) as context:
+ sdk.jobs.submit_job("env {}", SubmitJobQueryParams())
+
+ self.assertEqual(400, context.exception.status_code)
+ self.assertEqual("job submission failed", context.exception.message)
+ self.assertIsInstance(context.exception.__cause__,
httpx.HTTPStatusError)
+
+ def test_request_error_is_wrapped_as_connection_error(self):
+ request = httpx.Request("GET", "http://localhost:8080/overview")
+ session = RecordingSession(exc=httpx.RequestError("boom",
request=request))
+ sdk = self.create_client(session)
+
+ with self.assertRaises(SeaTunnelConnectionError) as context:
+ sdk.cluster.get_overview()
+
+ self.assertIn("http://localhost:8080/overview", str(context.exception))
+ self.assertIsInstance(context.exception.__cause__, httpx.RequestError)
+
+ def test_context_manager_closes_underlying_session(self):
+ response = httpx.Response(
+ 200,
+ text="metric 1",
+ headers={"Content-Type": "text/plain"},
+ request=httpx.Request("GET", "http://localhost:8080/openmetrics"),
+ )
+ session = RecordingSession(response=response)
+ sdk = self.create_client(session)
+
+ with sdk as client:
+ client.cluster.get_metrics()
+
+ self.assertTrue(session.closed)
+
+
+if __name__ == "__main__":
+ unittest.main()