truefoundry 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of truefoundry might be problematic. Click here for more details.

@@ -93,7 +93,10 @@ def timed_lru_cache(
93
93
 
94
94
 
95
95
  def poll_for_function(
96
- func: Callable[..., T], poll_after_secs: int = 5, *args, **kwargs
96
+ func: Callable[..., T],
97
+ poll_after_secs: int = 5,
98
+ *args,
99
+ **kwargs,
97
100
  ) -> Generator[T, None, None]:
98
101
  while True:
99
102
  yield func(*args, **kwargs)
@@ -14,6 +14,7 @@ from truefoundry.deploy._autogen.models import (
14
14
  SparkExecutorDynamicScaling,
15
15
  SparkExecutorFixedInstances,
16
16
  WorkbenchImage,
17
+ WorkflowAlert,
17
18
  )
18
19
  from truefoundry.deploy.lib.dao.application import (
19
20
  delete_application,
@@ -72,6 +73,7 @@ from truefoundry.deploy.v2.lib.patched_models import (
72
73
  CUDAVersion,
73
74
  DockerFileBuild,
74
75
  DynamicVolumeConfig,
76
+ Email,
75
77
  Endpoint,
76
78
  GcpTPU,
77
79
  GitHelmRepo,
@@ -109,6 +111,7 @@ from truefoundry.deploy.v2.lib.patched_models import (
109
111
  Schedule,
110
112
  SecretMount,
111
113
  ServiceAutoscaling,
114
+ SlackWebhook,
112
115
  SQSInputConfig,
113
116
  SQSOutputConfig,
114
117
  SQSQueueMetricConfig,
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: application.json
3
- # timestamp: 2025-03-10T17:05:18+00:00
3
+ # timestamp: 2025-03-27T11:40:57+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -212,6 +212,17 @@ class DynamicVolumeConfig(BaseModel):
212
212
  size: conint(ge=1, le=64000) = Field(..., description="Size of volume in Gi")
213
213
 
214
214
 
215
+ class Email(BaseModel):
216
+ type: Literal["email"] = Field(..., description="")
217
+ notification_channel: constr(min_length=1) = Field(
218
+ ..., description="Specify the notification channel to send alerts to"
219
+ )
220
+ to_emails: List[constr(min_length=1)] = Field(
221
+ ...,
222
+ description="List of recipients' email addresses if the notification channel is Email.",
223
+ )
224
+
225
+
215
226
  class Endpoint(BaseModel):
216
227
  host: constr(
217
228
  regex=r"^((([a-zA-Z0-9\-]{1,63}\.)([a-zA-Z0-9\-]{1,63}\.)*([A-Za-z]{1,63}))|(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))$"
@@ -349,25 +360,6 @@ class Image(BaseModel):
349
360
  )
350
361
 
351
362
 
352
- class JobAlert(BaseModel):
353
- """
354
- Describes the configuration for the job alerts
355
- """
356
-
357
- notification_channel: constr(min_length=1) = Field(
358
- ..., description="Specify the notification channel to send alerts to"
359
- )
360
- to_emails: Optional[List[constr(min_length=1)]] = Field(
361
- None,
362
- description="List of recipients' email addresses if the notification channel is Email.",
363
- )
364
- on_start: bool = Field(False, description="Send an alert when the job starts")
365
- on_completion: bool = Field(
366
- False, description="Send an alert when the job completes"
367
- )
368
- on_failure: bool = Field(True, description="Send an alert when the job fails")
369
-
370
-
371
363
  class Claim(BaseModel):
372
364
  key: str
373
365
  values: List[str]
@@ -389,6 +381,10 @@ class JwtAuthConfig(BaseModel):
389
381
  claims: Optional[List[Claim]] = Field(
390
382
  None, description="List of key-value pairs of claims to verify in the JWT token"
391
383
  )
384
+ bypass_auth_paths: Optional[List[constr(regex=r"^/[^*]*")]] = Field(
385
+ None,
386
+ description="List of paths that will bypass auth.\nneeds to start with a forward slash(/) and should not contain wildcards(*)",
387
+ )
392
388
 
393
389
 
394
390
  class KafkaMetricConfig(BaseModel):
@@ -510,15 +506,36 @@ class NvidiaGPU(BaseModel):
510
506
 
511
507
  class Profile(str, Enum):
512
508
  """
513
- Name of the MIG profile to use. One of [1g.5gb, 2g.10gb, 3g.20gb, 1g.10gb, 2g.20gb, 3g.40gb]
509
+ Name of the MIG profile to use. One of the following based on gpu type
510
+ Please refer to https://docs.nvidia.com/datacenter/tesla/mig-user-guide/#supported-mig-profiles for more details
511
+ A100 40 GB - [1g.5gb, 1g.10gb, 2g.10gb, 3g.20gb, 4g.20gb]
512
+ A100 80 GB / H100 80 GB - [1g.10gb, 1g.20gb, 2g.20gb, 3g.40gb, 4g.40gb]
513
+ H100 94 GB - [1g.12gb, 1g.24gb, 2g.24gb, 3g.47gb, 4g.47gb]
514
+ H100 96 GB - [1g.12gb, 1g.24gb, 2g.24gb, 3g.48gb, 4g.48gb]
515
+ H200 141 GB - [1g.18gb, 1g.35gb, 2g.35gb, 3g.71gb, 4g.71gb]
514
516
  """
515
517
 
516
518
  field_1g_5gb = "1g.5gb"
517
- field_2g_10gb = "2g.10gb"
518
- field_3g_20gb = "3g.20gb"
519
519
  field_1g_10gb = "1g.10gb"
520
+ field_1g_12gb = "1g.12gb"
521
+ field_1g_18gb = "1g.18gb"
522
+ field_1g_20gb = "1g.20gb"
523
+ field_1g_24gb = "1g.24gb"
524
+ field_1g_35gb = "1g.35gb"
525
+ field_2g_10gb = "2g.10gb"
520
526
  field_2g_20gb = "2g.20gb"
527
+ field_2g_24gb = "2g.24gb"
528
+ field_2g_35gb = "2g.35gb"
529
+ field_3g_20gb = "3g.20gb"
521
530
  field_3g_40gb = "3g.40gb"
531
+ field_3g_47gb = "3g.47gb"
532
+ field_3g_48gb = "3g.48gb"
533
+ field_3g_71gb = "3g.71gb"
534
+ field_4g_20gb = "4g.20gb"
535
+ field_4g_40gb = "4g.40gb"
536
+ field_4g_47gb = "4g.47gb"
537
+ field_4g_48gb = "4g.48gb"
538
+ field_4g_71gb = "4g.71gb"
522
539
 
523
540
 
524
541
  class NvidiaMIGGPU(BaseModel):
@@ -529,7 +546,7 @@ class NvidiaMIGGPU(BaseModel):
529
546
  )
530
547
  profile: Profile = Field(
531
548
  ...,
532
- description="Name of the MIG profile to use. One of [1g.5gb, 2g.10gb, 3g.20gb, 1g.10gb, 2g.20gb, 3g.40gb]",
549
+ description="Name of the MIG profile to use. One of the following based on gpu type\nPlease refer to https://docs.nvidia.com/datacenter/tesla/mig-user-guide/#supported-mig-profiles for more details\nA100 40 GB - [1g.5gb, 1g.10gb, 2g.10gb, 3g.20gb, 4g.20gb]\nA100 80 GB / H100 80 GB - [1g.10gb, 1g.20gb, 2g.20gb, 3g.40gb, 4g.40gb]\nH100 94 GB - [1g.12gb, 1g.24gb, 2g.24gb, 3g.47gb, 4g.47gb]\nH100 96 GB - [1g.12gb, 1g.24gb, 2g.24gb, 3g.48gb, 4g.48gb]\nH200 141 GB - [1g.18gb, 1g.35gb, 2g.35gb, 3g.71gb, 4g.71gb]",
533
550
  )
534
551
 
535
552
 
@@ -812,6 +829,13 @@ class ServiceAutoscaling(BaseAutoscaling):
812
829
  )
813
830
 
814
831
 
832
+ class SlackWebhook(BaseModel):
833
+ type: Literal["slack-webhook"] = Field(..., description="")
834
+ notification_channel: constr(min_length=1) = Field(
835
+ ..., description="Specify the notification channel to send alerts to"
836
+ )
837
+
838
+
815
839
  class SparkDriverConfig(BaseModel):
816
840
  ui_endpoint: Endpoint
817
841
  resources: Optional[Resources] = None
@@ -918,6 +942,10 @@ class TrueFoundryArtifactSource(BaseModel):
918
942
 
919
943
  class TrueFoundryInteractiveLogin(BaseModel):
920
944
  type: Literal["truefoundry_oauth"] = Field(..., description="")
945
+ bypass_auth_paths: Optional[List[constr(regex=r"^/[^*]*")]] = Field(
946
+ None,
947
+ description="List of paths that will bypass auth.\nneeds to start with a forward slash(/) and should not contain wildcards(*)",
948
+ )
921
949
 
922
950
 
923
951
  class VolumeBrowser(BaseModel):
@@ -1145,59 +1173,6 @@ class Helm(BaseModel):
1145
1173
  )
1146
1174
 
1147
1175
 
1148
- class Job(BaseModel):
1149
- """
1150
- Describes the configuration for the job
1151
- """
1152
-
1153
- type: Literal["job"] = Field(..., description="")
1154
- name: constr(regex=r"^[a-z](?:[a-z0-9]|-(?!-)){1,30}[a-z0-9]$") = Field(
1155
- ..., description="Name of the job"
1156
- )
1157
- image: Union[Build, Image] = Field(
1158
- ...,
1159
- description="Specify whether you want to deploy a Docker image or build and deploy from source code",
1160
- )
1161
- trigger: Union[Manual, Schedule] = Field(
1162
- {"type": "manual"}, description="Specify the trigger"
1163
- )
1164
- trigger_on_deploy: bool = Field(
1165
- False, description="Trigger the job after deploy immediately"
1166
- )
1167
- params: Optional[List[Param]] = Field(
1168
- None, description="Configure params and pass it to create different job runs"
1169
- )
1170
- env: Optional[Dict[str, str]] = Field(
1171
- None,
1172
- description="Configure environment variables to be injected in the service either as plain text or secrets. [Docs](https://docs.truefoundry.com/docs/env-variables)",
1173
- )
1174
- resources: Optional[Resources] = None
1175
- alerts: Optional[List[JobAlert]] = Field(
1176
- None,
1177
- description="Configure alerts to be sent when the job starts/fails/completes",
1178
- )
1179
- retries: conint(ge=0, le=10) = Field(
1180
- 0,
1181
- description="Specify the maximum number of attempts to retry a job before it is marked as failed.",
1182
- )
1183
- timeout: Optional[conint(le=432000, gt=0)] = Field(
1184
- None, description="Job timeout in seconds."
1185
- )
1186
- concurrency_limit: Optional[PositiveInt] = Field(
1187
- None, description="Number of runs that can run concurrently"
1188
- )
1189
- service_account: Optional[str] = Field(None, description="")
1190
- mounts: Optional[List[Union[SecretMount, StringDataMount, VolumeMount]]] = Field(
1191
- None,
1192
- description="Configure data to be mounted to job pod(s) as a string, secret or volume. [Docs](https://docs.truefoundry.com/docs/mounting-volumes-job)",
1193
- )
1194
- labels: Optional[Dict[str, str]] = Field(None, description="")
1195
- kustomize: Optional[Kustomize] = None
1196
- workspace_fqn: Optional[str] = Field(
1197
- None, description="Fully qualified name of the workspace"
1198
- )
1199
-
1200
-
1201
1176
  class KafkaInputConfig(BaseModel):
1202
1177
  """
1203
1178
  Describes the configuration for the input Kafka worker
@@ -1439,6 +1414,18 @@ class WorkerConfig(BaseModel):
1439
1414
  )
1440
1415
 
1441
1416
 
1417
+ class WorkflowAlert(BaseModel):
1418
+ """
1419
+ Describes the configuration for the workflow alerts
1420
+ """
1421
+
1422
+ notification_target: Optional[Union[Email, SlackWebhook]] = None
1423
+ on_completion: bool = Field(
1424
+ False, description="Send an alert when the job completes"
1425
+ )
1426
+ on_failure: bool = Field(True, description="Send an alert when the job fails")
1427
+
1428
+
1442
1429
  class BaseService(BaseModel):
1443
1430
  name: constr(regex=r"^[a-z](?:[a-z0-9]|-(?!-)){1,30}[a-z0-9]$") = Field(
1444
1431
  ...,
@@ -1487,6 +1474,26 @@ class FlyteTaskTemplate(BaseModel):
1487
1474
  custom: FlyteTaskCustom
1488
1475
 
1489
1476
 
1477
+ class JobAlert(BaseModel):
1478
+ """
1479
+ Describes the configuration for the job alerts
1480
+ """
1481
+
1482
+ notification_channel: Optional[constr(min_length=1)] = Field(
1483
+ None, description="Specify the notification channel to send alerts to"
1484
+ )
1485
+ to_emails: Optional[List[constr(min_length=1)]] = Field(
1486
+ None,
1487
+ description="List of recipients' email addresses if the notification channel is Email.",
1488
+ )
1489
+ notification_target: Optional[Union[Email, SlackWebhook]] = None
1490
+ on_start: bool = Field(False, description="Send an alert when the job starts")
1491
+ on_completion: bool = Field(
1492
+ False, description="Send an alert when the job completes"
1493
+ )
1494
+ on_failure: bool = Field(True, description="Send an alert when the job fails")
1495
+
1496
+
1490
1497
  class Service(BaseService):
1491
1498
  """
1492
1499
  Describes the configuration for the service
@@ -1528,6 +1535,59 @@ class FlyteTask(BaseModel):
1528
1535
  description: Optional[Any] = None
1529
1536
 
1530
1537
 
1538
+ class Job(BaseModel):
1539
+ """
1540
+ Describes the configuration for the job
1541
+ """
1542
+
1543
+ type: Literal["job"] = Field(..., description="")
1544
+ name: constr(regex=r"^[a-z](?:[a-z0-9]|-(?!-)){1,30}[a-z0-9]$") = Field(
1545
+ ..., description="Name of the job"
1546
+ )
1547
+ image: Union[Build, Image] = Field(
1548
+ ...,
1549
+ description="Specify whether you want to deploy a Docker image or build and deploy from source code",
1550
+ )
1551
+ trigger: Union[Manual, Schedule] = Field(
1552
+ {"type": "manual"}, description="Specify the trigger"
1553
+ )
1554
+ trigger_on_deploy: bool = Field(
1555
+ False, description="Trigger the job after deploy immediately"
1556
+ )
1557
+ params: Optional[List[Param]] = Field(
1558
+ None, description="Configure params and pass it to create different job runs"
1559
+ )
1560
+ env: Optional[Dict[str, str]] = Field(
1561
+ None,
1562
+ description="Configure environment variables to be injected in the service either as plain text or secrets. [Docs](https://docs.truefoundry.com/docs/env-variables)",
1563
+ )
1564
+ resources: Optional[Resources] = None
1565
+ alerts: Optional[List[JobAlert]] = Field(
1566
+ None,
1567
+ description="Configure alerts to be sent when the job starts/fails/completes",
1568
+ )
1569
+ retries: conint(ge=0, le=10) = Field(
1570
+ 0,
1571
+ description="Specify the maximum number of attempts to retry a job before it is marked as failed.",
1572
+ )
1573
+ timeout: Optional[conint(le=432000, gt=0)] = Field(
1574
+ None, description="Job timeout in seconds."
1575
+ )
1576
+ concurrency_limit: Optional[PositiveInt] = Field(
1577
+ None, description="Number of runs that can run concurrently"
1578
+ )
1579
+ service_account: Optional[str] = Field(None, description="")
1580
+ mounts: Optional[List[Union[SecretMount, StringDataMount, VolumeMount]]] = Field(
1581
+ None,
1582
+ description="Configure data to be mounted to job pod(s) as a string, secret or volume. [Docs](https://docs.truefoundry.com/docs/mounting-volumes-job)",
1583
+ )
1584
+ labels: Optional[Dict[str, str]] = Field(None, description="")
1585
+ kustomize: Optional[Kustomize] = None
1586
+ workspace_fqn: Optional[str] = Field(
1587
+ None, description="Fully qualified name of the workspace"
1588
+ )
1589
+
1590
+
1531
1591
  class Workflow(BaseModel):
1532
1592
  """
1533
1593
  Describes the configuration for the worflow
@@ -1546,6 +1606,7 @@ class Workflow(BaseModel):
1546
1606
  flyte_entities: Optional[List[Union[FlyteTask, FlyteWorkflow, FlyteLaunchPlan]]] = (
1547
1607
  Field(None, description="")
1548
1608
  )
1609
+ alerts: Optional[List[WorkflowAlert]] = Field(None, description="")
1549
1610
 
1550
1611
 
1551
1612
  class ApplicationSet(BaseModel):
@@ -14,7 +14,10 @@ from rich.status import Status
14
14
  from tqdm import tqdm
15
15
  from tqdm.utils import CallbackIOWrapper
16
16
 
17
- from truefoundry.common.constants import VERSION_PREFIX
17
+ from truefoundry.common.constants import (
18
+ SERVICEFOUNDRY_CLIENT_MAX_RETRIES,
19
+ VERSION_PREFIX,
20
+ )
18
21
  from truefoundry.common.request_utils import request_handling
19
22
  from truefoundry.common.servicefoundry_client import (
20
23
  ServiceFoundryServiceClient as BaseServiceFoundryServiceClient,
@@ -247,7 +250,7 @@ class ServiceFoundryServiceClient(BaseServiceFoundryServiceClient):
247
250
  callback: Optional[OutputCallBack] = None,
248
251
  ) -> socketio.Client:
249
252
  callback = callback or OutputCallBack()
250
- sio = socketio.Client(request_timeout=60)
253
+ sio = socketio.Client(request_timeout=60, reconnection_attempts=10)
251
254
  callback.print_line("Waiting for the task to start...")
252
255
  next_log_start_timestamp = query_dict.get("startTs")
253
256
 
@@ -286,6 +289,7 @@ class ServiceFoundryServiceClient(BaseServiceFoundryServiceClient):
286
289
  transports="websocket",
287
290
  headers=self._get_headers(),
288
291
  socketio_path=socketio_path,
292
+ retry=True,
289
293
  )
290
294
  return sio
291
295
 
@@ -298,10 +302,15 @@ class ServiceFoundryServiceClient(BaseServiceFoundryServiceClient):
298
302
 
299
303
  @check_min_cli_version
300
304
  def get_deployment_statuses(
301
- self, application_id: str, deployment_id: str
305
+ self,
306
+ application_id: str,
307
+ deployment_id: str,
308
+ retry_count: int = SERVICEFOUNDRY_CLIENT_MAX_RETRIES,
302
309
  ) -> List[AppDeploymentStatusResponse]:
303
310
  url = f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}/deployments/{deployment_id}/statuses"
304
- response = session_with_retries().get(url, headers=self._get_headers())
311
+ response = session_with_retries(retries=retry_count).get(
312
+ url, headers=self._get_headers()
313
+ )
305
314
  response_data = request_handling(response)
306
315
  return parse_obj_as(List[AppDeploymentStatusResponse], response_data)
307
316
 
@@ -107,12 +107,20 @@ def _tail_build_logs(build_response: BuildResponse) -> socketio.Client:
107
107
 
108
108
  def _deploy_wait_handler( # noqa: C901
109
109
  deployment: Deployment,
110
+ tail_logs: bool = True,
110
111
  ) -> Optional[DeploymentTransitionStatus]:
112
+ tail_logs_or_polling_status_message = (
113
+ "You can press Ctrl + C to exit the tailing of build logs "
114
+ )
115
+ if not tail_logs:
116
+ tail_logs_or_polling_status_message = (
117
+ "you can press Ctrl + C to exit the polling of deployment status "
118
+ )
111
119
  _log_application_dashboard_url(
112
120
  deployment=deployment,
113
121
  log_message=(
114
122
  "You can track the progress below or on the dashboard:- '%s'\n"
115
- "You can press Ctrl + C to exit the tailing of build logs "
123
+ f"{tail_logs_or_polling_status_message}"
116
124
  "and deployment will continue on the server"
117
125
  ),
118
126
  )
@@ -130,6 +138,7 @@ def _deploy_wait_handler( # noqa: C901
130
138
  poll_after_secs=poll_interval_seconds,
131
139
  application_id=deployment.applicationId,
132
140
  deployment_id=deployment.id,
141
+ retry_count=10,
133
142
  ):
134
143
  if len(deployment_statuses) == 0:
135
144
  logger.warning("Did not receive any deployment status")
@@ -157,12 +166,15 @@ def _deploy_wait_handler( # noqa: C901
157
166
  latest_deployment_status.transition
158
167
  == DeploymentTransitionStatus.BUILDING
159
168
  ):
160
- if not socket:
161
- build_responses = client.get_deployment_build_response(
162
- application_id=deployment.applicationId,
163
- deployment_id=deployment.id,
164
- )
165
- socket = _tail_build_logs(build_responses[0])
169
+ if tail_logs and not socket:
170
+ try:
171
+ build_responses = client.get_deployment_build_response(
172
+ application_id=deployment.applicationId,
173
+ deployment_id=deployment.id,
174
+ )
175
+ socket = _tail_build_logs(build_responses[0])
176
+ except Exception as e:
177
+ logger.error("Error tailing build logs: %s", e)
166
178
 
167
179
  time_elapsed = time.monotonic() - start_time
168
180
  if time_elapsed > total_timeout_time:
@@ -25,15 +25,17 @@ from truefoundry.deploy.lib.clients.servicefoundry_client import (
25
25
  ServiceFoundryServiceClient,
26
26
  )
27
27
  from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
28
- from truefoundry.deploy.lib.model.entity import Deployment
28
+ from truefoundry.deploy.lib.model.entity import Deployment, DeploymentTransitionStatus
29
+ from truefoundry.deploy.v2.lib.deploy import _deploy_wait_handler
29
30
  from truefoundry.deploy.v2.lib.source import (
30
31
  local_source_to_remote_source,
31
32
  )
32
33
  from truefoundry.logger import logger
33
34
  from truefoundry.pydantic_v1 import ValidationError
34
35
  from truefoundry.workflow.workflow import (
36
+ TRUEFOUNDRY_ALERTS_CONFIG,
35
37
  TRUEFOUNDRY_LAUNCH_PLAN_NAME,
36
- execution_config_store,
38
+ truefoundry_config_store,
37
39
  )
38
40
 
39
41
 
@@ -132,6 +134,7 @@ def _validate_workflow_entities( # noqa: C901
132
134
  `from truefoundry.workflow import PythonTaskConfig, ContainerTaskConfig`
133
135
  """
134
136
  tasks_without_truefoundry_worflow_package = []
137
+ task_names = set()
135
138
  for task in tasks:
136
139
  if _is_dynamic_task(task):
137
140
  raise ValueError("Dynamic workflows are not supported yet.")
@@ -141,6 +144,12 @@ def _validate_workflow_entities( # noqa: C901
141
144
  task.template.id.name
142
145
  )
143
146
  )
147
+ task_name = task.template.id.name
148
+ if task_name in task_names:
149
+ raise ValueError(
150
+ f"Task name should be unique, task with name {task_name} is repeated in the workflow"
151
+ )
152
+ task_names.add(task_name)
144
153
  task_image_spec = task.template.custom["truefoundry"]["image"]
145
154
  if task_image_spec["type"] == "task-python-build":
146
155
  is_tfy_wf_present_in_task_python_build = (
@@ -166,7 +175,7 @@ def _validate_workflow_entities( # noqa: C901
166
175
  # validate that all inputs have default values for cron workflows
167
176
  for launch_plan in launch_plans:
168
177
  if (
169
- execution_config_store.get(launch_plan.spec.workflow_id.name)
178
+ truefoundry_config_store.get(launch_plan.spec.workflow_id.name)
170
179
  and launch_plan.id.name == TRUEFOUNDRY_LAUNCH_PLAN_NAME
171
180
  ):
172
181
  workflow_inputs = launch_plan.spec.default_inputs.parameters
@@ -229,7 +238,7 @@ def _generate_manifest_for_workflow(
229
238
 
230
239
  # this is the case when someone has a cron schedule. and this line is for handling default launch plan in this case.
231
240
  if (
232
- execution_config_store.get(workflow_name)
241
+ truefoundry_config_store.get(workflow_name)
233
242
  and workflow_name == entity.id.name
234
243
  ):
235
244
  continue
@@ -255,6 +264,14 @@ def _generate_manifest_for_workflow(
255
264
 
256
265
  workflow.flyte_entities.append(message_dict)
257
266
 
267
+ alerts_config = truefoundry_config_store.get(TRUEFOUNDRY_ALERTS_CONFIG)
268
+ if alerts_config:
269
+ if not workflow.alerts:
270
+ workflow.alerts = alerts_config
271
+ else:
272
+ logger.warning(
273
+ "Alerts are configured in both workflow decorator as well as in deployment config. Alerts configured in workflow decorator will be ignored."
274
+ )
258
275
  # this step is just to verify if pydantic model is still valid after adding flyte_entities
259
276
  autogen_models.Workflow.validate({**workflow.dict()})
260
277
 
@@ -281,7 +298,7 @@ def deploy_workflow(
281
298
 
282
299
  # we need to rest the execution config store as it is a global variable and we don't want to keep the cron execution config for next workflow
283
300
  # this is only needed for notebook environment
284
- execution_config_store.reset()
301
+ truefoundry_config_store.reset()
285
302
 
286
303
  workspace_id = get_workspace_by_fqn(workspace_fqn).id
287
304
 
@@ -307,4 +324,17 @@ def deploy_workflow(
307
324
  deployment.fqn,
308
325
  )
309
326
  deployment_url = f"{client.tfy_host.strip('/')}/applications/{deployment.applicationId}?tab=deployments"
327
+ if wait:
328
+ try:
329
+ last_status_printed = _deploy_wait_handler(
330
+ deployment=deployment, tail_logs=False
331
+ )
332
+ if not last_status_printed or DeploymentTransitionStatus.is_failure_state(
333
+ last_status_printed
334
+ ):
335
+ deployment_tab_url = f"{client.tfy_host.strip('/')}/applications/{deployment.applicationId}?tab=deployments"
336
+ message = f"Deployment Failed. Please refer to the logs for additional details - {deployment_tab_url}"
337
+ sys.exit(message)
338
+ except KeyboardInterrupt:
339
+ logger.info("Ctrl-c executed. The deployment will still continue.")
310
340
  logger.info("You can find the application on the dashboard:- '%s'", deployment_url)
@@ -563,3 +563,11 @@ class TaskPythonBuild(models.TaskPythonBuild, PatchedModelBase):
563
563
 
564
564
  # Deprecated aliases, kept for backward compatibility
565
565
  TruefoundryArtifactSource = TrueFoundryArtifactSource
566
+
567
+
568
+ class Email(models.Email, PatchedModelBase):
569
+ type: Literal["email"] = "email"
570
+
571
+
572
+ class SlackWebhook(models.SlackWebhook, PatchedModelBase):
573
+ type: Literal["slack-webhook"] = "slack-webhook"
@@ -10,9 +10,11 @@ from flytekit.core.workflow import (
10
10
  )
11
11
  from flytekit.core.workflow import workflow as flytekit_workflow
12
12
 
13
+ from truefoundry.deploy._autogen.models import WorkflowAlert
13
14
  from truefoundry.pydantic_v1 import BaseModel
14
15
 
15
16
  TRUEFOUNDRY_LAUNCH_PLAN_NAME = "default"
17
+ TRUEFOUNDRY_ALERTS_CONFIG = "tfy_alerts_config"
16
18
 
17
19
 
18
20
  class ExecutionConfig(BaseModel):
@@ -22,7 +24,7 @@ class ExecutionConfig(BaseModel):
22
24
  schedule: str
23
25
 
24
26
 
25
- class ExecutionConfigStore(BaseModel):
27
+ class TruefoundryConfigStore(BaseModel):
26
28
  execution_config_map: Dict[str, ExecutionConfig] = {}
27
29
 
28
30
  def reset(self):
@@ -35,7 +37,7 @@ class ExecutionConfigStore(BaseModel):
35
37
  self.execution_config_map[key] = value
36
38
 
37
39
 
38
- execution_config_store = ExecutionConfigStore()
40
+ truefoundry_config_store = TruefoundryConfigStore()
39
41
 
40
42
 
41
43
  def workflow(
@@ -43,6 +45,7 @@ def workflow(
43
45
  failure_policy: Optional[WorkflowFailurePolicy] = None,
44
46
  on_failure: Optional[Task] = None,
45
47
  execution_configs: Optional[List[ExecutionConfig]] = None,
48
+ alerts: Optional[List[WorkflowAlert]] = None,
46
49
  ) -> Union[
47
50
  Callable[[Callable[..., FuncOut]], PythonFunctionWorkflow],
48
51
  PythonFunctionWorkflow,
@@ -85,6 +88,7 @@ def workflow(
85
88
  failure_policy=failure_policy,
86
89
  on_failure=on_failure,
87
90
  execution_configs=execution_configs,
91
+ alerts=alerts,
88
92
  )
89
93
 
90
94
  return wrapper
@@ -109,6 +113,9 @@ def workflow(
109
113
  function_module = _workflow_function.__module__
110
114
  function_name = _workflow_function.__name__
111
115
  function_path = f"{function_module}.{function_name}"
112
- execution_config_store.set(function_path, execution_config)
116
+ truefoundry_config_store.set(function_path, execution_config)
117
+
118
+ if alerts:
119
+ truefoundry_config_store.set(TRUEFOUNDRY_ALERTS_CONFIG, alerts)
113
120
 
114
121
  return workflow_object
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truefoundry
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: TrueFoundry CLI
5
5
  Author-email: TrueFoundry Team <abhishek@truefoundry.com>
6
6
  Requires-Python: <3.14,>=3.8.1
@@ -30,7 +30,7 @@ Requires-Dist: requirements-parser<0.12.0,>=0.11.0
30
30
  Requires-Dist: rich-click<2.0.0,>=1.2.1
31
31
  Requires-Dist: rich<14.0.0,>=13.7.1
32
32
  Requires-Dist: tqdm<5.0.0,>=4.0.0
33
- Requires-Dist: truefoundry-sdk==0.0.5
33
+ Requires-Dist: truefoundry-sdk==0.0.8
34
34
  Requires-Dist: typing-extensions>=4.0
35
35
  Requires-Dist: urllib3<3,>=1.26.18
36
36
  Requires-Dist: yq<4.0.0,>=3.1.0
@@ -46,11 +46,11 @@ truefoundry/common/servicefoundry_client.py,sha256=2fYhdVPSvLXz5C5tosOq86JD8WM3I
46
46
  truefoundry/common/session.py,sha256=xeBAPUNEJv2XVFQCRUGeBDTePh5zrKNSok8vmSxBjPw,2813
47
47
  truefoundry/common/storage_provider_utils.py,sha256=yURhMw8k0FLFvaviRHDiifhvc6GnuQwGMC9Qd2uM440,10934
48
48
  truefoundry/common/types.py,sha256=BMJFCsR1lPJAw66IQBSvLyV4I6o_x5oj78gVsUa9si8,188
49
- truefoundry/common/utils.py,sha256=Ev4lATS0jT9BDNPmQEP3aDn8IIeh9pmahv5pcNIjuuw,6366
49
+ truefoundry/common/utils.py,sha256=yEQtJW2fT9xbNpRhfRoD9hvhGw-FgGS3agh1oZptmjg,6379
50
50
  truefoundry/common/warnings.py,sha256=rs6BHwk7imQYedo07iwh3TWEOywAR3Lqhj0AY4khByg,504
51
- truefoundry/deploy/__init__.py,sha256=lso6tFZVNyB3KmjEZICq_Sd6R_Yip-67XAPXYAQCvGw,2597
51
+ truefoundry/deploy/__init__.py,sha256=zNsyzJAOPCPqlhVEEq6sBpfPsa4XkChaXvE-EBCmfzs,2645
52
52
  truefoundry/deploy/python_deploy_codegen.py,sha256=AainOFR20XvhNeztJkLPWGZ40lAT_nwc-ZmG77Kum4o,6525
53
- truefoundry/deploy/_autogen/models.py,sha256=oi0Q80dQsXCMVL3SJudogxaBVdIOvBQDEaOi791Dj-4,66926
53
+ truefoundry/deploy/_autogen/models.py,sha256=0m43YJVG0fBhLp0fPkCod_vIcMFob0jTQEGnjk6nOUQ,69804
54
54
  truefoundry/deploy/builder/__init__.py,sha256=nGQiR3r16iumRy7xbVQ6q-k0EApmijspsfVpXDE-9po,4953
55
55
  truefoundry/deploy/builder/constants.py,sha256=amUkHoHvVKzGv0v_knfiioRuKiJM0V0xW0diERgWiI0,508
56
56
  truefoundry/deploy/builder/docker_service.py,sha256=sm7GWeIqyrKaZpxskdLejZlsxcZnM3BTDJr6orvPN4E,3948
@@ -93,7 +93,7 @@ truefoundry/deploy/lib/session.py,sha256=-FX4gOtiGlc6Jk56JPVZpDqXR9xQza77AIlBvNJ
93
93
  truefoundry/deploy/lib/util.py,sha256=J7r8San2wKo48A7-BlH2-OKTlBO67zlPjLEhMsL8os0,1059
94
94
  truefoundry/deploy/lib/win32.py,sha256=1RcvPTdlOAJ48rt8rCbE2Ufha2ztRqBAE9dueNXArrY,5009
95
95
  truefoundry/deploy/lib/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
- truefoundry/deploy/lib/clients/servicefoundry_client.py,sha256=DO2uSfrlX_udIAQ6YM_l59OGBA75mhp3-6phbdLVSPc,26475
96
+ truefoundry/deploy/lib/clients/servicefoundry_client.py,sha256=NjNGmBsmrIfDZFfqJz12B4TVDHMZeAZDf-5eKSnS9zs,26697
97
97
  truefoundry/deploy/lib/dao/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  truefoundry/deploy/lib/dao/application.py,sha256=oMszpueXPUfTUuN_XdKwoRjQyqAgWHhZ-10cbprCVdM,9226
99
99
  truefoundry/deploy/lib/dao/apply.py,sha256=5IFERe5sLmZGlavaKTIxL4xPHAme4ZS2Ww0a2rKTyT0,3029
@@ -104,11 +104,11 @@ truefoundry/deploy/lib/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
104
104
  truefoundry/deploy/lib/model/entity.py,sha256=Up-DDOezkwM2tdqibfLdZO6jmT2pVq6SShB5sobBIGI,8531
105
105
  truefoundry/deploy/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
106
  truefoundry/deploy/v2/lib/__init__.py,sha256=WEiVMZXOVljzEE3tpGJil14liIn_PCDoACJ6b3tZ6sI,188
107
- truefoundry/deploy/v2/lib/deploy.py,sha256=23Sc2OxtAMvDzKfNmVv5vuaB6GqP-e1WAZWIezMzm3c,12413
108
- truefoundry/deploy/v2/lib/deploy_workflow.py,sha256=7NlL5eASwsDLcmsWhhh-YR-yBxS1CPwM-ceP87lNm_M,12860
107
+ truefoundry/deploy/v2/lib/deploy.py,sha256=FWmqX4N6RUN4-S68i-7xaIifYWnf0wc-zfINdpiFu-4,12901
108
+ truefoundry/deploy/v2/lib/deploy_workflow.py,sha256=G5BzMIbap8pgDX1eY-TITruUxQdkKhYtBmRwLL6lDeY,14342
109
109
  truefoundry/deploy/v2/lib/deployable_patched_models.py,sha256=xbHFD3pURflvCm8EODPvjfvRrv67mlSrjPUknY8SMB8,4060
110
110
  truefoundry/deploy/v2/lib/models.py,sha256=ogc1UYs1Z2nBdGSKCrde9sk8d0GxFKMkem99uqO5CmM,1148
111
- truefoundry/deploy/v2/lib/patched_models.py,sha256=Lowm-tyCGGv6-4iY9vfgmHrr4zPFy2u9wZEY3xQBv4E,16359
111
+ truefoundry/deploy/v2/lib/patched_models.py,sha256=vVjYs1Gm7mpuTx3C0l40RQ_zvMdQ1S0s6J-f00qO0nA,16557
112
112
  truefoundry/deploy/v2/lib/source.py,sha256=d6-8_6Zn5koBglqrBrY6ZLG_7yyPuLdyEmK4iZTw6xY,9405
113
113
  truefoundry/ml/__init__.py,sha256=EEEHV7w58Krpo_W9Chd8Y3TdItfFO3LI6j6Izqc4-P8,2219
114
114
  truefoundry/ml/constants.py,sha256=vDq72d4C9FSWqr9MMdjgTF4TuyNFApvo_6RVsSeAjB4,2837
@@ -366,12 +366,12 @@ truefoundry/workflow/container_task.py,sha256=8arieePsX4__OnG337hOtCiNgJwtKJJCsZ
366
366
  truefoundry/workflow/map_task.py,sha256=f9vcAPRQy0Ttw6bvdZBKUVJMSm4eGQrbE1GHWhepHIU,1864
367
367
  truefoundry/workflow/python_task.py,sha256=SRXRLC4vdBqGjhkwuaY39LEWN6iPCpJAuW17URRdWTY,1128
368
368
  truefoundry/workflow/task.py,sha256=34m55mALXx6ko9o5HkK6FDtMajdvJzBhOsHwDM2RcBA,1779
369
- truefoundry/workflow/workflow.py,sha256=WaTqUjhwfAXDWu4E5ehuwAxrCbDJkoAf1oWmR2E9Qy0,4575
369
+ truefoundry/workflow/workflow.py,sha256=OjKBwEArxTzNDpfJWgnIqkXDQrYQRLXjheRwpOCu3LE,4861
370
370
  truefoundry/workflow/remote_filesystem/__init__.py,sha256=LQ95ViEjJ7Ts4JcCGOxMPs7NZmQdZ4bTiq6qXtsjUhE,206
371
371
  truefoundry/workflow/remote_filesystem/logger.py,sha256=em2l7D6sw7xTLDP0kQSLpgfRRCLpN14Qw85TN7ujQcE,1022
372
372
  truefoundry/workflow/remote_filesystem/tfy_signed_url_client.py,sha256=xcT0wQmQlgzcj0nP3tJopyFSVWT1uv3nhiTIuwfXYeg,12342
373
373
  truefoundry/workflow/remote_filesystem/tfy_signed_url_fs.py,sha256=nSGPZu0Gyd_jz0KsEE-7w_BmnTD8CVF1S8cUJoxaCbc,13305
374
- truefoundry-0.6.1.dist-info/METADATA,sha256=Uyf15T-CVjXzv8YQG-s6MYAImwBlzpqSJM1hfEFB_jM,2349
375
- truefoundry-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
376
- truefoundry-0.6.1.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
377
- truefoundry-0.6.1.dist-info/RECORD,,
374
+ truefoundry-0.6.3.dist-info/METADATA,sha256=i3AnyI6S08k481_Ht6nvQvl4SXeDkV_9Sq8h_cONET4,2349
375
+ truefoundry-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
376
+ truefoundry-0.6.3.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
377
+ truefoundry-0.6.3.dist-info/RECORD,,