This is an automated email from the ASF dual-hosted git repository.
jin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-hugegraph-ai.git
The following commit(s) were added to refs/heads/main by this push:
new e356178 feat(vermeer): add vermeer python client for graph computing
(#263)
e356178 is described below
commit e3561782397b79a825832ad41b36634b070f2b58
Author: SoJGooo <[email protected]>
AuthorDate: Tue Jun 10 22:08:52 2025 +0800
feat(vermeer): add vermeer python client for graph computing (#263)
develop vermeer python client.
Support some graph computation algorithms.
---------
Co-authored-by: imbajin <[email protected]>
---
vermeer-python-client/README.md | 26 +++
vermeer-python-client/requirements.txt | 7 +
vermeer-python-client/setup.py | 42 ++++
vermeer-python-client/src/pyvermeer/__init__.py | 16 ++
vermeer-python-client/src/pyvermeer/api/base.py | 40 ++++
vermeer-python-client/src/pyvermeer/api/graph.py | 39 ++++
vermeer-python-client/src/pyvermeer/api/master.py | 16 ++
vermeer-python-client/src/pyvermeer/api/task.py | 49 ++++
vermeer-python-client/src/pyvermeer/api/worker.py | 16 ++
.../src/pyvermeer/client/__init__.py | 16 ++
.../src/pyvermeer/client/client.py | 63 +++++
.../src/pyvermeer/demo/task_demo.py | 53 +++++
.../src/pyvermeer/structure/__init__.py | 16 ++
.../src/pyvermeer/structure/base_data.py | 50 ++++
.../src/pyvermeer/structure/graph_data.py | 255 +++++++++++++++++++++
.../src/pyvermeer/structure/master_data.py | 82 +++++++
.../src/pyvermeer/structure/task_data.py | 226 ++++++++++++++++++
.../src/pyvermeer/structure/worker_data.py | 118 ++++++++++
.../src/pyvermeer/utils/__init__.py | 16 ++
.../src/pyvermeer/utils/exception.py | 44 ++++
vermeer-python-client/src/pyvermeer/utils/log.py | 70 ++++++
.../src/pyvermeer/utils/vermeer_config.py | 37 +++
.../src/pyvermeer/utils/vermeer_datetime.py | 32 +++
.../src/pyvermeer/utils/vermeer_requests.py | 115 ++++++++++
24 files changed, 1444 insertions(+)
diff --git a/vermeer-python-client/README.md b/vermeer-python-client/README.md
new file mode 100644
index 0000000..83c0cc2
--- /dev/null
+++ b/vermeer-python-client/README.md
@@ -0,0 +1,26 @@
+# vermeer-python-client
+
+The `vermeer-python-client` is a Python client(SDK) for
[Vermeer](https://github.com/apache/incubator-hugegraph-computer/tree/master/vermeer#readme)
(A high-performance distributed graph computing platform based on memory,
supporting more than 15 graph algorithms, custom algorithm extensions, and
custom data source access & easy to deploy and use)
+
+
+## Installation
+
+To install the `vermeer-python-client`, you can use `pip/uv` or **source code
building**:
+
+```bash
+#todo
+```
+
+### Install from Source (Latest Code)
+
+To install from the source, clone the repository and install the required
dependencies:
+
+```bash
+#todo
+```
+
+## Usage
+
+```bash
+#todo
+```
\ No newline at end of file
diff --git a/vermeer-python-client/requirements.txt
b/vermeer-python-client/requirements.txt
new file mode 100644
index 0000000..30b82d7
--- /dev/null
+++ b/vermeer-python-client/requirements.txt
@@ -0,0 +1,7 @@
+# TODO: replace pip/current file to uv
+decorator~=5.1.1
+requests~=2.32.0
+setuptools~=78.1.1
+urllib3~=2.2.2
+rich~=13.9.4
+python-dateutil~=2.9.0
diff --git a/vermeer-python-client/setup.py b/vermeer-python-client/setup.py
new file mode 100644
index 0000000..753d897
--- /dev/null
+++ b/vermeer-python-client/setup.py
@@ -0,0 +1,42 @@
+# 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 setuptools
+from pkg_resources import parse_requirements
+
+# TODO: replace/delete current file by uv
+with open("README.md", "r", encoding="utf-8") as fh:
+ long_description = fh.read()
+
+with open("requirements.txt", encoding="utf-8") as fp:
+ install_requires = [str(requirement) for requirement in
parse_requirements(fp)]
+
+setuptools.setup(
+ name="vermeer-python",
+ version="0.1.0",
+ install_requires=install_requires,
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ packages=setuptools.find_packages(where="src", exclude=["tests"]),
+ package_dir={"": "src"},
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: OS Independent",
+ ],
+ python_requires=">=3.9",
+)
diff --git a/vermeer-python-client/src/pyvermeer/__init__.py
b/vermeer-python-client/src/pyvermeer/__init__.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/vermeer-python-client/src/pyvermeer/api/base.py
b/vermeer-python-client/src/pyvermeer/api/base.py
new file mode 100644
index 0000000..0ab5fe0
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/api/base.py
@@ -0,0 +1,40 @@
+# 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 pyvermeer.utils.log import log
+
+
+class BaseModule:
+ """Base class"""
+
+ def __init__(self, client):
+ self._client = client
+ self.log = log.getChild(__name__)
+
+ @property
+ def session(self):
+ """Return the client's session object"""
+ return self._client.session
+
+ def _send_request(self, method: str, endpoint: str, params: dict = None):
+ """Unified request entry point"""
+ self.log.debug(f"Sending {method} to {endpoint}")
+ return self._client.send_request(
+ method=method,
+ endpoint=endpoint,
+ params=params
+ )
diff --git a/vermeer-python-client/src/pyvermeer/api/graph.py
b/vermeer-python-client/src/pyvermeer/api/graph.py
new file mode 100644
index 0000000..1fabdca
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/api/graph.py
@@ -0,0 +1,39 @@
+# 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 pyvermeer.structure.graph_data import GraphsResponse, GraphResponse
+from .base import BaseModule
+
+
+class GraphModule(BaseModule):
+ """Graph"""
+
+ def get_graph(self, graph_name: str) -> GraphResponse:
+ """Get task list"""
+ response = self._send_request(
+ "GET",
+ f"/graphs/{graph_name}"
+ )
+ return GraphResponse(response)
+
+ def get_graphs(self) -> GraphsResponse:
+ """Get task list"""
+ response = self._send_request(
+ "GET",
+ "/graphs",
+ )
+ return GraphsResponse(response)
diff --git a/vermeer-python-client/src/pyvermeer/api/master.py
b/vermeer-python-client/src/pyvermeer/api/master.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/api/master.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/vermeer-python-client/src/pyvermeer/api/task.py
b/vermeer-python-client/src/pyvermeer/api/task.py
new file mode 100644
index 0000000..12e1186
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/api/task.py
@@ -0,0 +1,49 @@
+# 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 pyvermeer.api.base import BaseModule
+
+from pyvermeer.structure.task_data import TasksResponse, TaskCreateRequest,
TaskCreateResponse, TaskResponse
+
+
+class TaskModule(BaseModule):
+ """Task"""
+
+ def get_tasks(self) -> TasksResponse:
+ """Get task list"""
+ response = self._send_request(
+ "GET",
+ "/tasks"
+ )
+ return TasksResponse(response)
+
+ def get_task(self, task_id: int) -> TaskResponse:
+ """Get single task information"""
+ response = self._send_request(
+ "GET",
+ f"/task/{task_id}"
+ )
+ return TaskResponse(response)
+
+ def create_task(self, create_task: TaskCreateRequest) ->
TaskCreateResponse:
+ """Create new task"""
+ response = self._send_request(
+ method="POST",
+ endpoint="/tasks/create",
+ params=create_task.to_dict()
+ )
+ return TaskCreateResponse(response)
diff --git a/vermeer-python-client/src/pyvermeer/api/worker.py
b/vermeer-python-client/src/pyvermeer/api/worker.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/api/worker.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/vermeer-python-client/src/pyvermeer/client/__init__.py
b/vermeer-python-client/src/pyvermeer/client/__init__.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/client/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/vermeer-python-client/src/pyvermeer/client/client.py
b/vermeer-python-client/src/pyvermeer/client/client.py
new file mode 100644
index 0000000..ba6a094
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/client/client.py
@@ -0,0 +1,63 @@
+# 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 Dict
+from typing import Optional
+
+from pyvermeer.api.base import BaseModule
+from pyvermeer.api.graph import GraphModule
+from pyvermeer.api.task import TaskModule
+from pyvermeer.utils.log import log
+from pyvermeer.utils.vermeer_config import VermeerConfig
+from pyvermeer.utils.vermeer_requests import VermeerSession
+
+
+class PyVermeerClient:
+ """Vermeer API Client"""
+
+ def __init__(
+ self,
+ ip: str,
+ port: int,
+ token: str,
+ timeout: Optional[tuple[float, float]] = None,
+ log_level: str = "INFO",
+ ):
+ """Initialize the client, including configuration and session
management
+ :param ip:
+ :param port:
+ :param token:
+ :param timeout:
+ :param log_level:
+ """
+ self.cfg = VermeerConfig(ip, port, token, timeout)
+ self.session = VermeerSession(self.cfg)
+ self._modules: Dict[str, BaseModule] = {
+ "graph": GraphModule(self),
+ "tasks": TaskModule(self)
+ }
+ log.setLevel(log_level)
+
+ def __getattr__(self, name):
+ """Access modules through attributes"""
+ if name in self._modules:
+ return self._modules[name]
+ raise AttributeError(f"Module {name} not found")
+
+ def send_request(self, method: str, endpoint: str, params: dict = None):
+ """Unified request method"""
+ return self.session.request(method, endpoint, params)
diff --git a/vermeer-python-client/src/pyvermeer/demo/task_demo.py
b/vermeer-python-client/src/pyvermeer/demo/task_demo.py
new file mode 100644
index 0000000..bb0a00d
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/demo/task_demo.py
@@ -0,0 +1,53 @@
+# 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 pyvermeer.client.client import PyVermeerClient
+from pyvermeer.structure.task_data import TaskCreateRequest
+
+
+def main():
+ """main"""
+ client = PyVermeerClient(
+ ip="127.0.0.1",
+ port=8688,
+ token="",
+ log_level="DEBUG",
+ )
+ task = client.tasks.get_tasks()
+
+ print(task.to_dict())
+
+ create_response = client.tasks.create_task(
+ create_task=TaskCreateRequest(
+ task_type='load',
+ graph_name='DEFAULT-example',
+ params={
+ "load.hg_pd_peers": "[\"127.0.0.1:8686\"]",
+ "load.hugegraph_name": "DEFAULT/example/g",
+ "load.hugegraph_password": "xxx",
+ "load.hugegraph_username": "xxx",
+ "load.parallel": "10",
+ "load.type": "hugegraph"
+ },
+ )
+ )
+
+ print(create_response.to_dict())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vermeer-python-client/src/pyvermeer/structure/__init__.py
b/vermeer-python-client/src/pyvermeer/structure/__init__.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/structure/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/vermeer-python-client/src/pyvermeer/structure/base_data.py
b/vermeer-python-client/src/pyvermeer/structure/base_data.py
new file mode 100644
index 0000000..4d60780
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/structure/base_data.py
@@ -0,0 +1,50 @@
+# 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.
+
+RESPONSE_ERR = 1
+RESPONSE_OK = 0
+RESPONSE_NONE = -1
+
+
+class BaseResponse(object):
+ """
+ Base response class
+ """
+
+ def __init__(self, dic: dict):
+ """
+ init
+ :param dic:
+ """
+ self.__errcode = dic.get('errcode', RESPONSE_NONE)
+ self.__message = dic.get('message', "")
+
+ @property
+ def errcode(self) -> int:
+ """
+ get error code
+ :return:
+ """
+ return self.__errcode
+
+ @property
+ def message(self) -> str:
+ """
+ get message
+ :return:
+ """
+ return self.__message
diff --git a/vermeer-python-client/src/pyvermeer/structure/graph_data.py
b/vermeer-python-client/src/pyvermeer/structure/graph_data.py
new file mode 100644
index 0000000..8f97ed1
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/structure/graph_data.py
@@ -0,0 +1,255 @@
+# 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 datetime
+
+from pyvermeer.structure.base_data import BaseResponse
+from pyvermeer.utils.vermeer_datetime import parse_vermeer_time
+
+
+class BackendOpt:
+ """BackendOpt class"""
+
+ def __init__(self, dic: dict):
+ """init"""
+ self.__vertex_data_backend = dic.get('vertex_data_backend', None)
+
+ @property
+ def vertex_data_backend(self):
+ """vertex data backend"""
+ return self.__vertex_data_backend
+
+ def to_dict(self):
+ """to dict"""
+ return {
+ 'vertex_data_backend': self.vertex_data_backend
+ }
+
+
+class GraphWorker:
+ """GraphWorker"""
+
+ def __init__(self, dic: dict):
+ """init"""
+ self.__name = dic.get('Name', '')
+ self.__vertex_count = dic.get('VertexCount', -1)
+ self.__vert_id_start = dic.get('VertIdStart', -1)
+ self.__edge_count = dic.get('EdgeCount', -1)
+ self.__is_self = dic.get('IsSelf', False)
+ self.__scatter_offset = dic.get('ScatterOffset', -1)
+
+ @property
+ def name(self) -> str:
+ """graph worker name"""
+ return self.__name
+
+ @property
+ def vertex_count(self) -> int:
+ """vertex count"""
+ return self.__vertex_count
+
+ @property
+ def vert_id_start(self) -> int:
+ """vertex id start"""
+ return self.__vert_id_start
+
+ @property
+ def edge_count(self) -> int:
+ """edge count"""
+ return self.__edge_count
+
+ @property
+ def is_self(self) -> bool:
+ """is self worker. Nonsense """
+ return self.__is_self
+
+ @property
+ def scatter_offset(self) -> int:
+ """scatter offset"""
+ return self.__scatter_offset
+
+ def to_dict(self):
+ """to dict"""
+ return {
+ 'name': self.name,
+ 'vertex_count': self.vertex_count,
+ 'vert_id_start': self.vert_id_start,
+ 'edge_count': self.edge_count,
+ 'is_self': self.is_self,
+ 'scatter_offset': self.scatter_offset
+ }
+
+
+class VermeerGraph:
+ """VermeerGraph"""
+
+ def __init__(self, dic: dict):
+ """init"""
+ self.__name = dic.get('name', '')
+ self.__space_name = dic.get('space_name', '')
+ self.__status = dic.get('status', '')
+ self.__create_time = parse_vermeer_time(dic.get('create_time', ''))
+ self.__update_time = parse_vermeer_time(dic.get('update_time', ''))
+ self.__vertex_count = dic.get('vertex_count', 0)
+ self.__edge_count = dic.get('edge_count', 0)
+ self.__workers = [GraphWorker(w) for w in dic.get('workers', [])]
+ self.__worker_group = dic.get('worker_group', '')
+ self.__use_out_edges = dic.get('use_out_edges', False)
+ self.__use_property = dic.get('use_property', False)
+ self.__use_out_degree = dic.get('use_out_degree', False)
+ self.__use_undirected = dic.get('use_undirected', False)
+ self.__on_disk = dic.get('on_disk', False)
+ self.__backend_option = BackendOpt(dic.get('backend_option', {}))
+
+ @property
+ def name(self) -> str:
+ """graph name"""
+ return self.__name
+
+ @property
+ def space_name(self) -> str:
+ """space name"""
+ return self.__space_name
+
+ @property
+ def status(self) -> str:
+ """graph status"""
+ return self.__status
+
+ @property
+ def create_time(self) -> datetime:
+ """create time"""
+ return self.__create_time
+
+ @property
+ def update_time(self) -> datetime:
+ """update time"""
+ return self.__update_time
+
+ @property
+ def vertex_count(self) -> int:
+ """vertex count"""
+ return self.__vertex_count
+
+ @property
+ def edge_count(self) -> int:
+ """edge count"""
+ return self.__edge_count
+
+ @property
+ def workers(self) -> list[GraphWorker]:
+ """graph workers"""
+ return self.__workers
+
+ @property
+ def worker_group(self) -> str:
+ """worker group"""
+ return self.__worker_group
+
+ @property
+ def use_out_edges(self) -> bool:
+ """whether graph has out edges"""
+ return self.__use_out_edges
+
+ @property
+ def use_property(self) -> bool:
+ """whether graph has property"""
+ return self.__use_property
+
+ @property
+ def use_out_degree(self) -> bool:
+ """whether graph has out degree"""
+ return self.__use_out_degree
+
+ @property
+ def use_undirected(self) -> bool:
+ """whether graph is undirected"""
+ return self.__use_undirected
+
+ @property
+ def on_disk(self) -> bool:
+ """whether graph is on disk"""
+ return self.__on_disk
+
+ @property
+ def backend_option(self) -> BackendOpt:
+ """backend option"""
+ return self.__backend_option
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ 'name': self.__name,
+ 'space_name': self.__space_name,
+ 'status': self.__status,
+ 'create_time': self.__create_time.strftime("%Y-%m-%d %H:%M:%S") if
self.__create_time else '',
+ 'update_time': self.__update_time.strftime("%Y-%m-%d %H:%M:%S") if
self.__update_time else '',
+ 'vertex_count': self.__vertex_count,
+ 'edge_count': self.__edge_count,
+ 'workers': [w.to_dict() for w in self.__workers],
+ 'worker_group': self.__worker_group,
+ 'use_out_edges': self.__use_out_edges,
+ 'use_property': self.__use_property,
+ 'use_out_degree': self.__use_out_degree,
+ 'use_undirected': self.__use_undirected,
+ 'on_disk': self.__on_disk,
+ 'backend_option': self.__backend_option.to_dict(),
+ }
+
+
+class GraphsResponse(BaseResponse):
+ """GraphsResponse"""
+
+ def __init__(self, dic: dict):
+ """init"""
+ super().__init__(dic)
+ self.__graphs = [VermeerGraph(g) for g in dic.get('graphs', [])]
+
+ @property
+ def graphs(self) -> list[VermeerGraph]:
+ """graphs"""
+ return self.__graphs
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ 'errcode': self.errcode,
+ 'message': self.message,
+ 'graphs': [g.to_dict() for g in self.graphs]
+ }
+
+
+class GraphResponse(BaseResponse):
+ """GraphResponse"""
+
+ def __init__(self, dic: dict):
+ """init"""
+ super().__init__(dic)
+ self.__graph = VermeerGraph(dic.get('graph', {}))
+
+ @property
+ def graph(self) -> VermeerGraph:
+ """graph"""
+ return self.__graph
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ 'errcode': self.errcode,
+ 'message': self.message,
+ 'graph': self.graph.to_dict()
+ }
diff --git a/vermeer-python-client/src/pyvermeer/structure/master_data.py
b/vermeer-python-client/src/pyvermeer/structure/master_data.py
new file mode 100644
index 0000000..de2fac7
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/structure/master_data.py
@@ -0,0 +1,82 @@
+# 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 datetime
+
+from pyvermeer.structure.base_data import BaseResponse
+from pyvermeer.utils.vermeer_datetime import parse_vermeer_time
+
+
+class MasterInfo:
+ """Master information"""
+
+ def __init__(self, dic: dict):
+ """Initialization function"""
+ self.__grpc_peer = dic.get('grpc_peer', '')
+ self.__ip_addr = dic.get('ip_addr', '')
+ self.__debug_mod = dic.get('debug_mod', False)
+ self.__version = dic.get('version', '')
+ self.__launch_time = parse_vermeer_time(dic.get('launch_time', ''))
+
+ @property
+ def grpc_peer(self) -> str:
+ """gRPC address"""
+ return self.__grpc_peer
+
+ @property
+ def ip_addr(self) -> str:
+ """IP address"""
+ return self.__ip_addr
+
+ @property
+ def debug_mod(self) -> bool:
+ """Whether it is debug mode"""
+ return self.__debug_mod
+
+ @property
+ def version(self) -> str:
+ """Master version number"""
+ return self.__version
+
+ @property
+ def launch_time(self) -> datetime:
+ """Master startup time"""
+ return self.__launch_time
+
+ def to_dict(self):
+ """Return data in dictionary format"""
+ return {
+ "grpc_peer": self.__grpc_peer,
+ "ip_addr": self.__ip_addr,
+ "debug_mod": self.__debug_mod,
+ "version": self.__version,
+ "launch_time": self.__launch_time.strftime("%Y-%m-%d %H:%M:%S") if
self.__launch_time else ''
+ }
+
+
+class MasterResponse(BaseResponse):
+ """Master response"""
+
+ def __init__(self, dic: dict):
+ """Initialization function"""
+ super().__init__(dic)
+ self.__master_info = MasterInfo(dic['master_info'])
+
+ @property
+ def master_info(self) -> MasterInfo:
+ """Get master node information"""
+ return self.__master_info
diff --git a/vermeer-python-client/src/pyvermeer/structure/task_data.py
b/vermeer-python-client/src/pyvermeer/structure/task_data.py
new file mode 100644
index 0000000..6fa408a
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/structure/task_data.py
@@ -0,0 +1,226 @@
+# 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 datetime
+
+from pyvermeer.structure.base_data import BaseResponse
+from pyvermeer.utils.vermeer_datetime import parse_vermeer_time
+
+
+class TaskWorker:
+ """task worker info"""
+
+ def __init__(self, dic):
+ """init"""
+ self.__name = dic.get('name', None)
+ self.__status = dic.get('status', None)
+
+ @property
+ def name(self) -> str:
+ """worker name"""
+ return self.__name
+
+ @property
+ def status(self) -> str:
+ """worker status"""
+ return self.__status
+
+ def to_dict(self):
+ """to dict"""
+ return {'name': self.name, 'status': self.status}
+
+
+class TaskInfo:
+ """task info"""
+
+ def __init__(self, dic):
+ """init"""
+ self.__id = dic.get('id', 0)
+ self.__status = dic.get('status', '')
+ self.__state = dic.get('state', '')
+ self.__create_user = dic.get('create_user', '')
+ self.__create_type = dic.get('create_type', '')
+ self.__create_time = parse_vermeer_time(dic.get('create_time', ''))
+ self.__start_time = parse_vermeer_time(dic.get('start_time', ''))
+ self.__update_time = parse_vermeer_time(dic.get('update_time', ''))
+ self.__graph_name = dic.get('graph_name', '')
+ self.__space_name = dic.get('space_name', '')
+ self.__type = dic.get('type', '')
+ self.__params = dic.get('params', {})
+ self.__workers = [TaskWorker(w) for w in dic.get('workers', [])]
+
+ @property
+ def id(self) -> int:
+ """task id"""
+ return self.__id
+
+ @property
+ def state(self) -> str:
+ """task state"""
+ return self.__state
+
+ @property
+ def create_user(self) -> str:
+ """task creator"""
+ return self.__create_user
+
+ @property
+ def create_type(self) -> str:
+ """task create type"""
+ return self.__create_type
+
+ @property
+ def create_time(self) -> datetime:
+ """task create time"""
+ return self.__create_time
+
+ @property
+ def start_time(self) -> datetime:
+ """task start time"""
+ return self.__start_time
+
+ @property
+ def update_time(self) -> datetime:
+ """task update time"""
+ return self.__update_time
+
+ @property
+ def graph_name(self) -> str:
+ """task graph"""
+ return self.__graph_name
+
+ @property
+ def space_name(self) -> str:
+ """task space"""
+ return self.__space_name
+
+ @property
+ def type(self) -> str:
+ """task type"""
+ return self.__type
+
+ @property
+ def params(self) -> dict:
+ """task params"""
+ return self.__params
+
+ @property
+ def workers(self) -> list[TaskWorker]:
+ """task workers"""
+ return self.__workers
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ 'id': self.__id,
+ 'status': self.__status,
+ 'state': self.__state,
+ 'create_user': self.__create_user,
+ 'create_type': self.__create_type,
+ 'create_time': self.__create_time.strftime("%Y-%m-%d %H:%M:%S") if
self.__start_time else '',
+ 'start_time': self.__start_time.strftime("%Y-%m-%d %H:%M:%S") if
self.__start_time else '',
+ 'update_time': self.__update_time.strftime("%Y-%m-%d %H:%M:%S") if
self.__update_time else '',
+ 'graph_name': self.__graph_name,
+ 'space_name': self.__space_name,
+ 'type': self.__type,
+ 'params': self.__params,
+ 'workers': [w.to_dict() for w in self.__workers],
+ }
+
+
+class TaskCreateRequest:
+ """task create request"""
+
+ def __init__(self, task_type, graph_name, params):
+ """init"""
+ self.task_type = task_type
+ self.graph_name = graph_name
+ self.params = params
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ 'task_type': self.task_type,
+ 'graph': self.graph_name,
+ 'params': self.params
+ }
+
+
+class TaskCreateResponse(BaseResponse):
+ """task create response"""
+
+ def __init__(self, dic):
+ """init"""
+ super().__init__(dic)
+ self.__task = TaskInfo(dic.get('task', {}))
+
+ @property
+ def task(self) -> TaskInfo:
+ """task info"""
+ return self.__task
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ "errcode": self.errcode,
+ "message": self.message,
+ "task": self.task.to_dict(),
+ }
+
+
+class TasksResponse(BaseResponse):
+ """tasks response"""
+
+ def __init__(self, dic):
+ """init"""
+ super().__init__(dic)
+ self.__tasks = [TaskInfo(t) for t in dic.get('tasks', [])]
+
+ @property
+ def tasks(self) -> list[TaskInfo]:
+ """task infos"""
+ return self.__tasks
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ "errcode": self.errcode,
+ "message": self.message,
+ "tasks": [t.to_dict() for t in self.tasks]
+ }
+
+
+class TaskResponse(BaseResponse):
+ """task response"""
+
+ def __init__(self, dic):
+ """init"""
+ super().__init__(dic)
+ self.__task = TaskInfo(dic.get('task', {}))
+
+ @property
+ def task(self) -> TaskInfo:
+ """task info"""
+ return self.__task
+
+ def to_dict(self) -> dict:
+ """to dict"""
+ return {
+ "errcode": self.errcode,
+ "message": self.message,
+ "task": self.task.to_dict(),
+ }
diff --git a/vermeer-python-client/src/pyvermeer/structure/worker_data.py
b/vermeer-python-client/src/pyvermeer/structure/worker_data.py
new file mode 100644
index 0000000..a35cfe9
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/structure/worker_data.py
@@ -0,0 +1,118 @@
+# 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 datetime
+
+from pyvermeer.structure.base_data import BaseResponse
+from pyvermeer.utils.vermeer_datetime import parse_vermeer_time
+
+
+class Worker:
+ """worker data"""
+
+ def __init__(self, dic):
+ """init"""
+ self.__id = dic.get('id', 0)
+ self.__name = dic.get('name', '')
+ self.__grpc_addr = dic.get('grpc_addr', '')
+ self.__ip_addr = dic.get('ip_addr', '')
+ self.__state = dic.get('state', '')
+ self.__version = dic.get('version', '')
+ self.__group = dic.get('group', '')
+ self.__init_time = parse_vermeer_time(dic.get('init_time', ''))
+ self.__launch_time = parse_vermeer_time(dic.get('launch_time', ''))
+
+ @property
+ def id(self) -> int:
+ """worker id"""
+ return self.__id
+
+ @property
+ def name(self) -> str:
+ """worker name"""
+ return self.__name
+
+ @property
+ def grpc_addr(self) -> str:
+ """gRPC address"""
+ return self.__grpc_addr
+
+ @property
+ def ip_addr(self) -> str:
+ """IP address"""
+ return self.__ip_addr
+
+ @property
+ def state(self) -> int:
+ """worker status"""
+ return self.__state
+
+ @property
+ def version(self) -> str:
+ """worker version"""
+ return self.__version
+
+ @property
+ def group(self) -> str:
+ """worker group"""
+ return self.__group
+
+ @property
+ def init_time(self) -> datetime:
+ """worker initialization time"""
+ return self.__init_time
+
+ @property
+ def launch_time(self) -> datetime:
+ """worker launch time"""
+ return self.__launch_time
+
+ def to_dict(self):
+ """convert object to dictionary"""
+ return {
+ "id": self.id,
+ "name": self.name,
+ "grpc_addr": self.grpc_addr,
+ "ip_addr": self.ip_addr,
+ "state": self.state,
+ "version": self.version,
+ "group": self.group,
+ "init_time": self.init_time,
+ "launch_time": self.launch_time,
+ }
+
+
+class WorkersResponse(BaseResponse):
+ """response of workers"""
+
+ def __init__(self, dic):
+ """init"""
+ super().__init__(dic)
+ self.__workers = [Worker(worker) for worker in dic['workers']]
+
+ @property
+ def workers(self) -> list[Worker]:
+ """list of workers"""
+ return self.__workers
+
+ def to_dict(self):
+ """convert object to dictionary"""
+ return {
+ "errcode": self.errcode,
+ "message": self.message,
+ "workers": [worker.to_dict() for worker in self.workers],
+ }
diff --git a/vermeer-python-client/src/pyvermeer/utils/__init__.py
b/vermeer-python-client/src/pyvermeer/utils/__init__.py
new file mode 100644
index 0000000..e0533d9
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/utils/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/vermeer-python-client/src/pyvermeer/utils/exception.py
b/vermeer-python-client/src/pyvermeer/utils/exception.py
new file mode 100644
index 0000000..ddb36d8
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/utils/exception.py
@@ -0,0 +1,44 @@
+# 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.
+
+
+class ConnectError(Exception):
+ """Raised when there is an issue connecting to the server."""
+
+ def __init__(self, message):
+ super().__init__(f"Connection error: {str(message)}")
+
+
+class TimeOutError(Exception):
+ """Raised when a request times out."""
+
+ def __init__(self, message):
+ super().__init__(f"Request timed out: {str(message)}")
+
+
+class JsonDecodeError(Exception):
+ """Raised when the response from the server cannot be decoded as JSON."""
+
+ def __init__(self, message):
+ super().__init__(f"Failed to decode JSON response: {str(message)}")
+
+
+class UnknownError(Exception):
+ """Raised for any other unknown errors."""
+
+ def __init__(self, message):
+ super().__init__(f"Unknown API error: {str(message)}")
diff --git a/vermeer-python-client/src/pyvermeer/utils/log.py
b/vermeer-python-client/src/pyvermeer/utils/log.py
new file mode 100644
index 0000000..cc5199f
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/utils/log.py
@@ -0,0 +1,70 @@
+# 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 logging
+import sys
+
+
+class VermeerLogger:
+ """vermeer API log"""
+ _instance = None
+
+ def __new__(cls, name: str = "VermeerClient"):
+ """new api logger"""
+ if cls._instance is None:
+ cls._instance = super().__new__(cls)
+ cls._instance._initialize(name)
+ return cls._instance
+
+ def _initialize(self, name: str):
+ """Initialize log configuration"""
+ self.logger = logging.getLogger(name)
+ self.logger.setLevel(logging.INFO) # Default level
+
+ if not self.logger.handlers:
+ # Console output format
+ console_format = logging.Formatter(
+ '[%(asctime)s] [%(levelname)s] %(name)s - %(message)s',
+ datefmt='%Y-%m-%d %H:%M:%S'
+ )
+
+ # Console handler
+ console_handler = logging.StreamHandler(sys.stdout)
+ console_handler.setLevel(logging.INFO) # Console default level
+ console_handler.setFormatter(console_format)
+
+ # file_handler = logging.FileHandler('api_client.log')
+ # file_handler.setLevel(logging.DEBUG)
+ # file_handler.setFormatter(
+ # logging.Formatter(
+ # '[%(asctime)s] [%(levelname)s] [%(threadName)s] %(name)s
- %(message)s'
+ # )
+ # )
+
+ self.logger.addHandler(console_handler)
+ # self.logger.addHandler(file_handler)
+
+ self.logger.propagate = False
+
+ @classmethod
+ def get_logger(cls) -> logging.Logger:
+ """Get configured logger"""
+ return cls().logger
+
+
+# Global log instance
+log = VermeerLogger.get_logger()
diff --git a/vermeer-python-client/src/pyvermeer/utils/vermeer_config.py
b/vermeer-python-client/src/pyvermeer/utils/vermeer_config.py
new file mode 100644
index 0000000..dfd4d50
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/utils/vermeer_config.py
@@ -0,0 +1,37 @@
+# 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.
+
+
+class VermeerConfig:
+ """The configuration of a Vermeer instance."""
+ ip: str
+ port: int
+ token: str
+ factor: str
+ username: str
+ graph_space: str
+
+ def __init__(self,
+ ip: str,
+ port: int,
+ token: str,
+ timeout: tuple[float, float] = (0.5, 15.0)):
+ """Initialize the configuration for a Vermeer instance."""
+ self.ip = ip
+ self.port = port
+ self.token = token
+ self.timeout = timeout
diff --git a/vermeer-python-client/src/pyvermeer/utils/vermeer_datetime.py
b/vermeer-python-client/src/pyvermeer/utils/vermeer_datetime.py
new file mode 100644
index 0000000..a76dfee
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/utils/vermeer_datetime.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.
+
+import datetime
+
+from dateutil import parser
+
+
+def parse_vermeer_time(vm_dt: str) -> datetime:
+ """Parse a vermeer time string into a Python datetime object."""
+ if vm_dt is None or len(vm_dt) == 0:
+ return None
+ dt = parser.parse(vm_dt)
+ return dt
+
+
+if __name__ == '__main__':
+
print(parse_vermeer_time('2025-02-17T15:45:05.396311145+08:00').strftime("%Y%m%d"))
diff --git a/vermeer-python-client/src/pyvermeer/utils/vermeer_requests.py
b/vermeer-python-client/src/pyvermeer/utils/vermeer_requests.py
new file mode 100644
index 0000000..118484c
--- /dev/null
+++ b/vermeer-python-client/src/pyvermeer/utils/vermeer_requests.py
@@ -0,0 +1,115 @@
+# 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 urllib.parse import urljoin
+
+import requests
+from requests.adapters import HTTPAdapter
+from urllib3.util.retry import Retry
+
+from pyvermeer.utils.exception import JsonDecodeError, ConnectError,
TimeOutError, UnknownError
+from pyvermeer.utils.log import log
+from pyvermeer.utils.vermeer_config import VermeerConfig
+
+
+class VermeerSession:
+ """vermeer session"""
+
+ def __init__(
+ self,
+ cfg: VermeerConfig,
+ retries: int = 3,
+ backoff_factor: int = 0.1,
+ status_forcelist=(500, 502, 504),
+ session: Optional[requests.Session] = None,
+ ):
+ """
+ Initialize the Session.
+ """
+ self._cfg = cfg
+ self._retries = retries
+ self._backoff_factor = backoff_factor
+ self._status_forcelist = status_forcelist
+ if self._cfg.token is not None:
+ self._auth = self._cfg.token
+ else:
+ raise ValueError("Vermeer Token must be provided.")
+ self._headers = {"Content-Type": "application/json", "Authorization":
self._auth}
+ self._timeout = cfg.timeout
+ self._session = session if session else requests.Session()
+ self.__configure_session()
+
+ def __configure_session(self):
+ """
+ Configure the retry strategy and connection adapter for the session.
+ """
+ retry_strategy = Retry(
+ total=self._retries,
+ read=self._retries,
+ connect=self._retries,
+ backoff_factor=self._backoff_factor,
+ status_forcelist=self._status_forcelist,
+ )
+ adapter = HTTPAdapter(max_retries=retry_strategy)
+ self._session.mount("http://", adapter)
+ self._session.mount("https://", adapter)
+ self._session.keep_alive = False
+ log.debug(
+ "Session configured with retries=%s and backoff_factor=%s",
+ self._retries,
+ self._backoff_factor,
+ )
+
+ def resolve(self, path: str):
+ """
+ Resolve the path to a full URL.
+ """
+ url = f"http://{self._cfg.ip}:{self._cfg.port}/"
+ return urljoin(url, path).strip("/")
+
+ def close(self):
+ """
+ closes the session.
+ """
+ self._session.close()
+
+ def request(
+ self,
+ method: str,
+ path: str,
+ params: dict = None
+ ) -> dict:
+ """request"""
+ try:
+ log.debug(f"Request made to {path} with params
{json.dumps(params)}")
+ response = self._session.request(method,
+ self.resolve(path),
+ headers=self._headers,
+ data=json.dumps(params),
+ timeout=self._timeout)
+ log.debug(f"Response code:{response.status_code}, received:
{response.text}")
+ return response.json()
+ except requests.ConnectionError as e:
+ raise ConnectError(e) from e
+ except requests.Timeout as e:
+ raise TimeOutError(e) from e
+ except json.JSONDecodeError as e:
+ raise JsonDecodeError(e) from e
+ except Exception as e:
+ raise UnknownError(e) from e