From 1c2abcda231b2d10e7a358a4e4b7973785cb6199 Mon Sep 17 00:00:00 2001 From: Stephan Fitzpatrick Date: Wed, 22 Nov 2023 23:39:38 -0800 Subject: [PATCH] Feat(python): Quality of Life improvements to Python client (#2686) * Fix references to `Windmill.start_script_async` * Improve client usability and extend functionalities This commit introduces several changes to improve the usability of the module and extend its functionalities. Line breaks have been added for long function calls to improve readability. A state setter has been added for the 'state' property to facilitate state updates. New functions 'cancel_running' and 'run_script' have been added to provide more control to the user over the script executions. The README file has been updated to reflect these changes and provide more comprehensive usage instructions including both basic and advanced usage of the module. * Add line breaks and update README.md for readability Inserted line breaks into function calls for improved readability in python-client/wmill/README.md. This enhances module usability by making code easier to follow. 'state' property setter, 'cancel_running' and 'run_script' functions have been added to enrich module's functionality. README was updated to reflect these changes and provide clearer instructions. --- python-client/wmill/README.md | 71 ++++++++++++++++++++++++++--- python-client/wmill/wmill/client.py | 44 ++++++++++++++++-- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/python-client/wmill/README.md b/python-client/wmill/README.md index 2bb1066fc4..75bac013d0 100644 --- a/python-client/wmill/README.md +++ b/python-client/wmill/README.md @@ -3,22 +3,81 @@ The core client for the [Windmill](https://windmill.dev) platform. -## Quickstart +## Usage + +### Basic Usage + +The `wmill` package has several methods at the top-level for the most frequent operations you will need. + +The following are some common examples: ```python +import time + import wmill def main(): - client = wmill.Windmill( + # Get the value of a variable + wmill.get_variable("u/user/variable_path") + + # Run a script synchronously and get the result + wmill.run_script("f/pathto/script", args={"arg1": "value1"}) + + # Get the value of a resource + wmill.get_resource("u/user/resource_path") + + # Set the script's state + wmill.set_state({"ts": time.time()}) + + # Get the script's state + wmill.get_state() +``` + +### Advanced Usage + +The `wmill` package also exposes the `Windmill` class, which is the core client for the Windmill platform. + +```python +import time + +from wmill import Windmill + +def main(): + client = Windmill( # token=... <- this is optional. otherwise the client will look for the WM_TOKEN env var ) - print(client.version) - print(client.get("u/user/resource_path")) + # Get the current version of the client + client.version + # Get the current user + client.user + + # Convenience get and post methods exist for https://app.windmill.dev/openapi.html#/ + # these are thin wrappers around the httpx library's get and post methods + # list worker groups + client.get("/configs/list_worker_groups") + # create a group + client.post( + f"/w/{client.workspace}/groups/create", + json={ + "name": "my-group", + "summary": "my group summary", + } + ) + + # Get and set the state of the script + now = time.time() + client.state = {"ts": now} + assert client.state == {"ts": now} + + # Run a job asynchronously job_id = client.run_script_async(path="path/to/script") - print(job_id) + # Get its status + client.get_job_status(job_id) + # Get its result + client.get_result(job_id) + - return client.run_script(path="path/to/script", args={"arg1": "value1"}) ``` diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index 7dcd69f3ec..fea2a7fa7a 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -228,7 +228,9 @@ class Windmill: def set_variable(self, path: str, value: str) -> None: """Set variable from Windmill""" # check if variable exists - r = self.get(f"/w/{self.workspace}/variables/get/{path}", raise_for_status=False) + r = self.get( + f"/w/{self.workspace}/variables/get/{path}", raise_for_status=False + ) if r.status_code == 404: # create variable self.post( @@ -254,7 +256,9 @@ class Windmill: ) -> str | None: """Get resource from Windmill""" try: - return self.get(f"/w/{self.workspace}/resources/get_value_interpolated/{path}").json() + return self.get( + f"/w/{self.workspace}/resources/get_value_interpolated/{path}" + ).json() except Exception as e: if none_if_undefined: return None @@ -268,7 +272,9 @@ class Windmill: resource_type: str, ): # check if resource exists - r = self.get(f"/w/{self.workspace}/resources/get/{path}", raise_for_status=False) + r = self.get( + f"/w/{self.workspace}/resources/get/{path}", raise_for_status=False + ) if r.status_code == 404: # create resource self.post( @@ -355,6 +361,10 @@ class Windmill: def state(self) -> Any: return self.get_resource(path=self.state_path, none_if_undefined=True) + @state.setter + def state(self, value: Any) -> None: + self.set_state(value) + @staticmethod def set_shared_state_pickle(value: Any, path: str = "state.pickle") -> None: """ @@ -631,3 +641,31 @@ def get_state_path() -> str: @init_global_client def get_resume_urls(approver: str = None) -> dict: return _client.get_resume_urls(approver) + + +@init_global_client +def cancel_running() -> dict: + """Cancel currently running executions of the same script.""" + return _client.cancel_running() + + +@init_global_client +def run_script( + path: str = None, + hash_: str = None, + args: dict = None, + timeout: dt.timedelta | int | float = None, + verbose: bool = False, + cleanup: bool = True, + assert_result_is_not_none: bool = True, +) -> Any: + """Run script synchronously and return its result.""" + return _client.run_script( + path=path, + hash_=hash_, + args=args, + verbose=verbose, + assert_result_is_not_none=assert_result_is_not_none, + cleanup=cleanup, + timeout=timeout, + )