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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wmill
3
- Version: 1.413.1
3
+ Version: 1.450.1
4
4
  Summary: A client library for accessing Windmill server wrapping the Windmill client API
5
5
  Home-page: https://windmill.dev
6
6
  License: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "wmill"
3
- version = "1.413.1"
3
+ version = "1.450.1"
4
4
  description = "A client library for accessing Windmill server wrapping the Windmill client API"
5
5
  license = "Apache-2.0"
6
6
  homepage = "https://windmill.dev"
@@ -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 ) -> Any:
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(f"{self.workspace}", self.client, s3object["s3"], s3_resource_path, s3object["storage"] if "storage" in s3object else None)
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 "storage" in s3object and s3object["storage"] is not None:
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