wmill 1.413.1__tar.gz → 1.450.1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {wmill-1.413.1 → wmill-1.450.1}/PKG-INFO +1 -1
- {wmill-1.413.1 → wmill-1.450.1}/pyproject.toml +1 -1
- {wmill-1.413.1 → wmill-1.450.1}/wmill/client.py +124 -7
- {wmill-1.413.1 → wmill-1.450.1}/README.md +0 -0
- {wmill-1.413.1 → wmill-1.450.1}/wmill/__init__.py +0 -0
- {wmill-1.413.1 → wmill-1.450.1}/wmill/py.typed +0 -0
- {wmill-1.413.1 → wmill-1.450.1}/wmill/s3_reader.py +0 -0
- {wmill-1.413.1 → wmill-1.450.1}/wmill/s3_types.py +0 -0
@@ -9,6 +9,7 @@ import os
|
|
9
9
|
import random
|
10
10
|
import time
|
11
11
|
import warnings
|
12
|
+
import json
|
12
13
|
from json import JSONDecodeError
|
13
14
|
from typing import Dict, Any, Union, Literal
|
14
15
|
|
@@ -26,7 +27,7 @@ JobStatus = Literal["RUNNING", "WAITING", "COMPLETED"]
|
|
26
27
|
|
27
28
|
class Windmill:
|
28
29
|
def __init__(self, base_url=None, token=None, workspace=None, verify=True):
|
29
|
-
base = base_url or os.environ.get("BASE_INTERNAL_URL")
|
30
|
+
base = base_url or os.environ.get("BASE_INTERNAL_URL") or os.environ.get("WM_BASE_URL")
|
30
31
|
|
31
32
|
self.base_url = f"{base}/api"
|
32
33
|
self.token = token or os.environ.get("WM_TOKEN")
|
@@ -358,15 +359,15 @@ class Windmill:
|
|
358
359
|
"percent": value,
|
359
360
|
"flow_job_id": flow_id or None,
|
360
361
|
},
|
361
|
-
)
|
362
|
+
)
|
362
363
|
|
363
|
-
def get_progress(self, job_id: Optional[str] = None
|
364
|
+
def get_progress(self, job_id: Optional[str] = None) -> Any:
|
364
365
|
workspace = get_workspace()
|
365
366
|
job_id = job_id or os.environ.get("WM_JOB_ID")
|
366
367
|
|
367
368
|
r = self.get(
|
368
369
|
f"/w/{workspace}/job_metrics/get_progress/{job_id}",
|
369
|
-
)
|
370
|
+
)
|
370
371
|
if r.status_code == 404:
|
371
372
|
print(f"Job {job_id} does not exist")
|
372
373
|
return None
|
@@ -472,7 +473,13 @@ class Windmill:
|
|
472
473
|
print(file_reader.read())
|
473
474
|
'''
|
474
475
|
"""
|
475
|
-
reader = S3BufferedReader(
|
476
|
+
reader = S3BufferedReader(
|
477
|
+
f"{self.workspace}",
|
478
|
+
self.client,
|
479
|
+
s3object["s3"],
|
480
|
+
s3_resource_path,
|
481
|
+
s3object["storage"] if "storage" in s3object else None,
|
482
|
+
)
|
476
483
|
return reader
|
477
484
|
|
478
485
|
def write_s3_file(
|
@@ -480,6 +487,8 @@ class Windmill:
|
|
480
487
|
s3object: S3Object | None,
|
481
488
|
file_content: BufferedReader | bytes,
|
482
489
|
s3_resource_path: str | None,
|
490
|
+
content_type: str | None = None,
|
491
|
+
content_disposition: str | None = None,
|
483
492
|
) -> S3Object:
|
484
493
|
"""
|
485
494
|
Write a file to the workspace S3 bucket
|
@@ -511,8 +520,12 @@ class Windmill:
|
|
511
520
|
query_params["file_key"] = s3object["s3"]
|
512
521
|
if s3_resource_path is not None and s3_resource_path != "":
|
513
522
|
query_params["s3_resource_path"] = s3_resource_path
|
514
|
-
if s3object is not None and
|
523
|
+
if s3object is not None and "storage" in s3object and s3object["storage"] is not None:
|
515
524
|
query_params["storage"] = s3object["storage"]
|
525
|
+
if content_type is not None:
|
526
|
+
query_params["content_type"] = content_type
|
527
|
+
if content_disposition is not None:
|
528
|
+
query_params["content_disposition"] = content_disposition
|
516
529
|
|
517
530
|
try:
|
518
531
|
# need a vanilla client b/c content-type is not application/json here
|
@@ -611,6 +624,83 @@ class Windmill:
|
|
611
624
|
params={"approver": approver},
|
612
625
|
).json()
|
613
626
|
|
627
|
+
def request_interactive_slack_approval(
|
628
|
+
self,
|
629
|
+
slack_resource_path: str,
|
630
|
+
channel_id: str,
|
631
|
+
message: str = None,
|
632
|
+
approver: str = None,
|
633
|
+
default_args_json: dict = None,
|
634
|
+
dynamic_enums_json: dict = None,
|
635
|
+
) -> None:
|
636
|
+
"""
|
637
|
+
Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
|
638
|
+
|
639
|
+
**[Enterprise Edition Only]** To include form fields in the Slack approval request, use the "Advanced -> Suspend -> Form" functionality.
|
640
|
+
Learn more at: https://www.windmill.dev/docs/flows/flow_approval#form
|
641
|
+
|
642
|
+
:param slack_resource_path: The path to the Slack resource in Windmill.
|
643
|
+
:type slack_resource_path: str
|
644
|
+
:param channel_id: The Slack channel ID where the approval request will be sent.
|
645
|
+
:type channel_id: str
|
646
|
+
:param message: Optional custom message to include in the Slack approval request.
|
647
|
+
:type message: str, optional
|
648
|
+
:param approver: Optional user ID or name of the approver for the request.
|
649
|
+
:type approver: str, optional
|
650
|
+
:param default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
|
651
|
+
:type default_args_json: dict, optional
|
652
|
+
:param dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
|
653
|
+
:type dynamic_enums_json: dict, optional
|
654
|
+
|
655
|
+
:raises Exception: If the function is not called within a flow or flow preview.
|
656
|
+
:raises Exception: If the required flow job or flow step environment variables are not set.
|
657
|
+
|
658
|
+
:return: None
|
659
|
+
|
660
|
+
**Usage Example:**
|
661
|
+
>>> client.request_interactive_slack_approval(
|
662
|
+
... slack_resource_path="/u/alex/my_slack_resource",
|
663
|
+
... channel_id="admins-slack-channel",
|
664
|
+
... message="Please approve this request",
|
665
|
+
... approver="approver123",
|
666
|
+
... default_args_json={"key1": "value1", "key2": 42},
|
667
|
+
... dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]},
|
668
|
+
... )
|
669
|
+
|
670
|
+
**Notes:**
|
671
|
+
- This function must be executed within a Windmill flow or flow preview.
|
672
|
+
- The function checks for required environment variables (`WM_FLOW_JOB_ID`, `WM_FLOW_STEP_ID`) to ensure it is run in the appropriate context.
|
673
|
+
"""
|
674
|
+
workspace = self.workspace
|
675
|
+
flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
|
676
|
+
|
677
|
+
if not flow_job_id:
|
678
|
+
raise Exception(
|
679
|
+
"You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
|
680
|
+
)
|
681
|
+
|
682
|
+
# Only include non-empty parameters
|
683
|
+
params = {}
|
684
|
+
if message:
|
685
|
+
params["message"] = message
|
686
|
+
if approver:
|
687
|
+
params["approver"] = approver
|
688
|
+
if slack_resource_path:
|
689
|
+
params["slack_resource_path"] = slack_resource_path
|
690
|
+
if channel_id:
|
691
|
+
params["channel_id"] = channel_id
|
692
|
+
if os.environ.get("WM_FLOW_STEP_ID"):
|
693
|
+
params["flow_step_id"] = os.environ.get("WM_FLOW_STEP_ID")
|
694
|
+
if default_args_json:
|
695
|
+
params["default_args_json"] = json.dumps(default_args_json)
|
696
|
+
if dynamic_enums_json:
|
697
|
+
params["dynamic_enums_json"] = json.dumps(dynamic_enums_json)
|
698
|
+
|
699
|
+
self.get(
|
700
|
+
f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}",
|
701
|
+
params=params,
|
702
|
+
)
|
703
|
+
|
614
704
|
def username_to_email(self, username: str) -> str:
|
615
705
|
"""
|
616
706
|
Get email from workspace username
|
@@ -817,11 +907,19 @@ def write_s3_file(
|
|
817
907
|
s3object: S3Object | None,
|
818
908
|
file_content: BufferedReader | bytes,
|
819
909
|
s3_resource_path: str | None = None,
|
910
|
+
content_type: str | None = None,
|
911
|
+
content_disposition: str | None = None,
|
820
912
|
) -> S3Object:
|
821
913
|
"""
|
822
914
|
Upload a file to S3
|
915
|
+
|
916
|
+
Content type will be automatically guessed from path extension if left empty
|
917
|
+
|
918
|
+
See MDN for content_disposition: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
|
919
|
+
and content_type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
|
920
|
+
|
823
921
|
"""
|
824
|
-
return _client.write_s3_file(s3object, file_content, s3_resource_path if s3_resource_path != "" else None)
|
922
|
+
return _client.write_s3_file(s3object, file_content, s3_resource_path if s3_resource_path != "" else None, content_type, content_disposition)
|
825
923
|
|
826
924
|
|
827
925
|
@init_global_client
|
@@ -865,6 +963,7 @@ def set_state(value: Any) -> None:
|
|
865
963
|
"""
|
866
964
|
return _client.set_state(value)
|
867
965
|
|
966
|
+
|
868
967
|
@init_global_client
|
869
968
|
def set_progress(value: int, job_id: Optional[str] = None) -> None:
|
870
969
|
"""
|
@@ -872,6 +971,7 @@ def set_progress(value: int, job_id: Optional[str] = None) -> None:
|
|
872
971
|
"""
|
873
972
|
return _client.set_progress(value, job_id)
|
874
973
|
|
974
|
+
|
875
975
|
@init_global_client
|
876
976
|
def get_progress(job_id: Optional[str] = None) -> Any:
|
877
977
|
"""
|
@@ -950,6 +1050,23 @@ def get_state_path() -> str:
|
|
950
1050
|
def get_resume_urls(approver: str = None) -> dict:
|
951
1051
|
return _client.get_resume_urls(approver)
|
952
1052
|
|
1053
|
+
@init_global_client
|
1054
|
+
def request_interactive_slack_approval(
|
1055
|
+
slack_resource_path: str,
|
1056
|
+
channel_id: str,
|
1057
|
+
message: str = None,
|
1058
|
+
approver: str = None,
|
1059
|
+
default_args_json: dict = None,
|
1060
|
+
dynamic_enums_json: dict = None,
|
1061
|
+
) -> None:
|
1062
|
+
return _client.request_interactive_slack_approval(
|
1063
|
+
slack_resource_path=slack_resource_path,
|
1064
|
+
channel_id=channel_id,
|
1065
|
+
message=message,
|
1066
|
+
approver=approver,
|
1067
|
+
default_args_json=default_args_json,
|
1068
|
+
dynamic_enums_json=dynamic_enums_json,
|
1069
|
+
)
|
953
1070
|
|
954
1071
|
@init_global_client
|
955
1072
|
def cancel_running() -> dict:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|