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()


Reply via email to