truefoundry 0.3.0rc4__tar.gz → 0.3.0rc6__tar.gz

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.

Files changed (137) hide show
  1. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/PKG-INFO +3 -3
  2. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/pyproject.toml +2 -2
  3. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/auto_gen/models.py +99 -40
  4. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/clients/servicefoundry_client.py +0 -2
  5. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/dao/application.py +0 -3
  6. truefoundry-0.3.0rc6/truefoundry/deploy/v2/lib/deploy_workflow.py +173 -0
  7. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/lib/deployable_patched_models.py +12 -3
  8. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/lib/patched_models.py +2 -2
  9. truefoundry-0.3.0rc6/truefoundry/version.py +4 -0
  10. truefoundry-0.3.0rc6/truefoundry/workflow/__init__.py +19 -0
  11. truefoundry-0.3.0rc6/truefoundry/workflow/container_task.py +12 -0
  12. truefoundry-0.3.0rc6/truefoundry/workflow/example/deploy.sh +1 -0
  13. truefoundry-0.3.0rc6/truefoundry/workflow/example/hello_world_package/workflow.py +19 -0
  14. truefoundry-0.3.0rc6/truefoundry/workflow/example/package/test_workflow.py +131 -0
  15. truefoundry-0.3.0rc6/truefoundry/workflow/example/truefoundry.yaml +9 -0
  16. truefoundry-0.3.0rc6/truefoundry/workflow/example/workflow.yaml +116 -0
  17. truefoundry-0.3.0rc6/truefoundry/workflow/map_task.py +45 -0
  18. truefoundry-0.3.0rc6/truefoundry/workflow/python_task.py +32 -0
  19. truefoundry-0.3.0rc6/truefoundry/workflow/task.py +50 -0
  20. truefoundry-0.3.0rc6/truefoundry/workflow/workflow.py +54 -0
  21. truefoundry-0.3.0rc4/truefoundry/flyte/__init__.py +0 -6
  22. truefoundry-0.3.0rc4/truefoundry/version.py +0 -6
  23. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/README.md +0 -0
  24. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/__init__.py +0 -0
  25. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/__init__.py +0 -0
  26. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/agents/__init__.py +0 -0
  27. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/agents/base.py +0 -0
  28. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/agents/developer.py +0 -0
  29. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/agents/project_identifier.py +0 -0
  30. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/agents/tester.py +0 -0
  31. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/cli.py +0 -0
  32. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/constants.py +0 -0
  33. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/exception.py +0 -0
  34. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/logger.py +0 -0
  35. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/__init__.py +0 -0
  36. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/ask.py +0 -0
  37. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/base.py +0 -0
  38. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/commit.py +0 -0
  39. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/docker_build.py +0 -0
  40. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/docker_run.py +0 -0
  41. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/file_type_counts.py +0 -0
  42. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/list_files.py +0 -0
  43. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/read_file.py +0 -0
  44. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/send_request.py +0 -0
  45. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/tools/write_file.py +0 -0
  46. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/utils/client.py +0 -0
  47. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/utils/diff.py +0 -0
  48. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/autodeploy/utils/pydantic_compat.py +0 -0
  49. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/cli/__init__.py +0 -0
  50. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/cli/__main__.py +0 -0
  51. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/__init__.py +0 -0
  52. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/__init__.py +0 -0
  53. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/builders/__init__.py +0 -0
  54. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/builders/dockerfile.py +0 -0
  55. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py +0 -0
  56. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py +0 -0
  57. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +0 -0
  58. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +0 -0
  59. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/builder/docker_service.py +0 -0
  60. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/__init__.py +0 -0
  61. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/cli.py +0 -0
  62. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/__init__.py +0 -0
  63. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/apply_command.py +0 -0
  64. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/build_command.py +0 -0
  65. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/build_logs_command.py +0 -0
  66. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/create_command.py +0 -0
  67. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/delete_command.py +0 -0
  68. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/deploy_command.py +0 -0
  69. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/get_command.py +0 -0
  70. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/list_command.py +0 -0
  71. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/login_command.py +0 -0
  72. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/logout_command.py +0 -0
  73. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/logs_command.py +0 -0
  74. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/patch_application_command.py +0 -0
  75. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/patch_command.py +0 -0
  76. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/redeploy_command.py +0 -0
  77. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/terminate_comand.py +0 -0
  78. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/commands/trigger_command.py +0 -0
  79. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/config.py +0 -0
  80. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/console.py +0 -0
  81. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/const.py +0 -0
  82. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/display_util.py +0 -0
  83. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/cli/util.py +0 -0
  84. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/core/__init__.py +0 -0
  85. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/core/login.py +0 -0
  86. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/core/logout.py +0 -0
  87. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/__init__.py +0 -0
  88. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/__main__.py +0 -0
  89. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/app.py +0 -0
  90. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/build.py +0 -0
  91. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/remote/__init__.py +0 -0
  92. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/remote/context.py +0 -0
  93. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/remote/method.py +0 -0
  94. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/remote/remote.py +0 -0
  95. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/route.py +0 -0
  96. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/service.py +0 -0
  97. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/function_service/utils.py +0 -0
  98. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/io/__init__.py +0 -0
  99. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/io/output_callback.py +0 -0
  100. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/io/rich_output_callback.py +0 -0
  101. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/json_util.py +0 -0
  102. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/__init__.py +0 -0
  103. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/auth/auth_service_client.py +0 -0
  104. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/auth/credential_file_manager.py +0 -0
  105. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/auth/credential_provider.py +0 -0
  106. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/auth/servicefoundry_session.py +0 -0
  107. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/clients/__init__.py +0 -0
  108. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/clients/shell_client.py +0 -0
  109. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/clients/utils.py +0 -0
  110. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/const.py +0 -0
  111. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/dao/__init__.py +0 -0
  112. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/dao/apply.py +0 -0
  113. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/dao/version.py +0 -0
  114. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/dao/workspace.py +0 -0
  115. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/exceptions.py +0 -0
  116. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/logs_utils.py +0 -0
  117. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/messages.py +0 -0
  118. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/model/__init__.py +0 -0
  119. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/model/entity.py +0 -0
  120. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/session.py +0 -0
  121. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/util.py +0 -0
  122. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/lib/win32.py +0 -0
  123. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/__init__.py +0 -0
  124. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/lib/__init__.py +0 -0
  125. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/lib/deploy.py +0 -0
  126. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/lib/models.py +0 -0
  127. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/deploy/v2/lib/source.py +0 -0
  128. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/langchain/__init__.py +0 -0
  129. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/langchain/deprecated.py +0 -0
  130. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/langchain/truefoundry_chat.py +0 -0
  131. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/langchain/truefoundry_embeddings.py +0 -0
  132. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/langchain/truefoundry_llm.py +0 -0
  133. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/langchain/utils.py +0 -0
  134. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/logger.py +0 -0
  135. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/ml/__init__.py +0 -0
  136. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/pydantic_v1.py +0 -0
  137. {truefoundry-0.3.0rc4 → truefoundry-0.3.0rc6}/truefoundry/python_deploy_codegen.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: truefoundry
3
- Version: 0.3.0rc4
3
+ Version: 0.3.0rc6
4
4
  Summary: Truefoundry CLI
5
5
  Author: Abhishek Choudhary
6
6
  Author-email: abhishek@truefoundry.com
@@ -10,8 +10,8 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
- Provides-Extra: flyte
14
13
  Provides-Extra: ml
14
+ Provides-Extra: workflow
15
15
  Requires-Dist: GitPython (>=3.1.43,<3.2.0)
16
16
  Requires-Dist: Mako (>=1.1.6,<2.0.0)
17
17
  Requires-Dist: PyJWT (>=2.0.0,<3.0.0)
@@ -20,7 +20,7 @@ Requires-Dist: click (>=7.0.0,<9.0.0)
20
20
  Requires-Dist: docker (>=6.1.2,<8.0.0)
21
21
  Requires-Dist: fastapi (>=0.56.0,<0.200.0)
22
22
  Requires-Dist: filelock (>=3.8.0,<4.0.0)
23
- Requires-Dist: flytekit (==1.12.2) ; extra == "flyte"
23
+ Requires-Dist: flytekit (==1.12.2) ; extra == "workflow"
24
24
  Requires-Dist: gitignorefile (>=1.1.2,<1.2.0)
25
25
  Requires-Dist: importlib-metadata (>=6.0.1,<8.0.0)
26
26
  Requires-Dist: importlib-resources (>=5.2.0,<6.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "truefoundry"
3
- version = "0.3.0rc4"
3
+ version = "0.3.0rc6"
4
4
  description = "Truefoundry CLI"
5
5
  authors = ["Abhishek Choudhary <abhishek@truefoundry.com>"]
6
6
  readme = "README.md"
@@ -39,7 +39,7 @@ yq = "^3.1.0"
39
39
 
40
40
  [tool.poetry.extras]
41
41
  ml = ["mlfoundry"]
42
- flyte = ["flytekit"]
42
+ workflow = ["flytekit"]
43
43
 
44
44
  [tool.poetry.group.dev.dependencies]
45
45
  ruff = ">=0.4.10"
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: application.json
3
- # timestamp: 2024-07-05T13:46:01+00:00
3
+ # timestamp: 2024-07-19T06:56:20+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -349,6 +349,25 @@ class Endpoint(BaseModel):
349
349
  )
350
350
 
351
351
 
352
+ class FlyteLaunchPlanID(BaseModel):
353
+ resourceType: Literal["LAUNCH_PLAN"]
354
+ name: str
355
+
356
+
357
+ class FlyteTaskID(BaseModel):
358
+ resourceType: Literal["TASK"]
359
+ name: str
360
+
361
+
362
+ class FlyteWorkflowID(BaseModel):
363
+ resourceType: Literal["WORKFLOW"]
364
+ name: str
365
+
366
+
367
+ class FlyteWorkflowTemplate(BaseModel):
368
+ id: FlyteWorkflowID
369
+
370
+
352
371
  class GcpTPU(BaseModel):
353
372
  type: Literal["gcp_tpu"] = Field(..., description="+value=gcp_tpu")
354
373
  name: constr(regex=r"^tpu-[a-z\d\-]+$") = Field(
@@ -431,14 +450,14 @@ class HttpProbe(BaseModel):
431
450
  +label=Instructions for assessing container health by executing an HTTP GET request.
432
451
  """
433
452
 
434
- type: Literal["http"] = Field(..., description="+sort=1\n+value=http")
453
+ type: Literal["http"] = Field(
454
+ ..., description="+sort=1\n+label=Request Type\n+value=http"
455
+ )
435
456
  path: str = Field(
436
- ...,
437
- description="+usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.\n+sort=2",
457
+ ..., description="+usage=Path to the health check endpoint\n+sort=2"
438
458
  )
439
459
  port: conint(ge=0, le=65535) = Field(
440
- ...,
441
- description="+usage=The TCP socket within the container to which the HTTP GET request should be directed.\n+sort=3",
460
+ ..., description="+usage=Listening port for the health check endpoint\n+sort=3"
442
461
  )
443
462
  host: Optional[str] = Field(
444
463
  None,
@@ -795,7 +814,7 @@ class PythonBuild(BaseModel):
795
814
  )
796
815
  command: Union[str, List[str]] = Field(
797
816
  ...,
798
- description="Command will be set as the Entrypoint of the generatede\nimage.\n+label=Command\n+usage=Command to run when the container starts.\nCommand will be set as the Entrypoint of the generated image.\nWhen deploying a Job, the command can be templatized by defining `params` and referencing them in command\nE.g. `python main.py --learning_rate {{learning_rate}}`",
817
+ description="Command will be set as the Entrypoint of the generated\nimage.\n+label=Command\n+usage=Command to run when the container starts.\nCommand will be set as the Entrypoint of the generated image.\nWhen deploying a Job, the command can be templatized by defining `params` and referencing them in command\nE.g. `python main.py --learning_rate {{learning_rate}}`",
799
818
  )
800
819
  cuda_version: Optional[
801
820
  constr(
@@ -1058,6 +1077,10 @@ class TaskDockerFileBuild(BaseModel):
1058
1077
  """
1059
1078
 
1060
1079
  type: Literal["task-dockerfile-build"] = Field(..., description="+value=dockerfile")
1080
+ docker_registry: Optional[str] = Field(
1081
+ None,
1082
+ description="+docs=FQN of the container registry. You can the FQN of your desired container registry (or add one)\nin the Integrations page[Integrations](https://app.truefoundry.tech/integrations?tab=docker-registry) page\n+label=Docker Registry\n+usage=FQN of the container registry. If you can't find your registry here,\nadd it through the [Integrations](/integrations?tab=docker-registry) page",
1083
+ )
1061
1084
  dockerfile_path: str = Field(
1062
1085
  "./Dockerfile",
1063
1086
  description="+label=Path to Dockerfile\n+usage=The file path of the Dockerfile relative to project root path.",
@@ -1077,6 +1100,10 @@ class TaskPythonBuild(BaseModel):
1077
1100
  type: Literal["task-python-build"] = Field(
1078
1101
  ..., description="+value=task-python-build"
1079
1102
  )
1103
+ docker_registry: Optional[str] = Field(
1104
+ None,
1105
+ description="+docs=FQN of the container registry. You can the FQN of your desired container registry (or add one)\nin the Integrations page[Integrations](https://app.truefoundry.tech/integrations?tab=docker-registry) page\n+label=Docker Registry\n+usage=FQN of the container registry. If you can't find your registry here,\nadd it through the [Integrations](/integrations?tab=docker-registry) page",
1106
+ )
1080
1107
  python_version: constr(regex=r"^\d+(\.\d+){1,2}([\-\.a-z0-9]+)?$") = Field(
1081
1108
  "3.9",
1082
1109
  description="+label=Python version\n+usage=Python version to run your application. Should be one of the tags listed on [Official Python Docker Page](https://hub.docker.com/_/python)\n+message=Please enter a valid Python version tag",
@@ -1238,34 +1265,6 @@ class VolumeMount(BaseModel):
1238
1265
  )
1239
1266
 
1240
1267
 
1241
- class Workflow(BaseModel):
1242
- """
1243
- +docs=Describes the configuration for the worflow
1244
- """
1245
-
1246
- type: constr(regex=r"^workflow$") = Field(..., description="+value=workflow")
1247
- name: constr(regex=r"^[a-z][a-z0-9\-]{1,30}[a-z0-9]$") = Field(
1248
- ...,
1249
- description="+usage=Name of the workflow\n+sort=1\n+message=3 to 32 lower case characters long alphanumeric word, may contain - in between, cannot start with a number",
1250
- )
1251
- source: Union[LocalSource, RemoteSource] = Field(
1252
- ...,
1253
- description="+docs=Source Code for the workflow, either local or remote\n+label=Source Code for your workflow\n+icon=fa-solid fa-cloud-arrow-up:#21B6A8\n+sort=200",
1254
- )
1255
- env: Optional[Dict[str, str]] = Field(
1256
- None,
1257
- description="+label=Environment Variables\n+usage=Configure environment variables to be injected in the service either as plain text or secrets. [Docs](https://docs.truefoundry.com/docs/env-variables)\n+icon=fa-globe\n+sort=500",
1258
- )
1259
- docker_registry: Optional[str] = Field(
1260
- None,
1261
- description="+docs=FQN of the container registry. You can the FQN of your desired container registry (or add one)\nin the Integrations page[Integrations](https://app.truefoundry.tech/integrations?tab=docker-registry) page\n+label=Docker Registry\n+usage=FQN of the container registry. If you can't find your registry here,\nadd it through the [Integrations](/integrations?tab=docker-registry) page",
1262
- )
1263
- flyte_entities: List[Dict[str, Any]] = Field(
1264
- ...,
1265
- description="+docs=These\n+label=Flyte Entities\nActually ContainerTaskConfig and PythonTaskConfig are not flyte entities, this is just for code generation",
1266
- )
1267
-
1268
-
1269
1268
  class ArtifactsDownload(BaseModel):
1270
1269
  """
1271
1270
  +docs=Describes the configuration for the artifacts cache
@@ -1381,6 +1380,9 @@ class ContainerTaskConfig(BaseModel):
1381
1380
  description="+label=Environment Variables\n+usage=Configure environment variables to be injected in the task either as plain text or secrets. [Docs](https://docs.truefoundry.com/docs/env-variables)\n+icon=fa-globe\n+sort=200",
1382
1381
  )
1383
1382
  resources: Optional[Resources] = None
1383
+ service_account: Optional[str] = Field(
1384
+ None, description="+label=Service Account\n+sort=400"
1385
+ )
1384
1386
 
1385
1387
 
1386
1388
  class CoreNATSOutputConfig(BaseModel):
@@ -1400,31 +1402,42 @@ class CoreNATSOutputConfig(BaseModel):
1400
1402
  auth: Optional[NATSUserPasswordAuth] = None
1401
1403
 
1402
1404
 
1405
+ class FlyteLaunchPlanSpec(BaseModel):
1406
+ workflowId: FlyteWorkflowID
1407
+
1408
+
1409
+ class FlyteWorkflow(BaseModel):
1410
+ template: FlyteWorkflowTemplate
1411
+ description: Optional[Any] = None
1412
+
1413
+
1403
1414
  class HealthProbe(BaseModel):
1404
1415
  """
1405
1416
  +docs=Describes the configuration for the Health Probe's
1406
1417
  To learn more you can go [here](https://docs.truefoundry.com/docs/liveness-readiness-probe)
1407
1418
  +icon=fa-heart
1419
+ +uiType=HealthProbe
1408
1420
  """
1409
1421
 
1410
1422
  config: HttpProbe
1411
1423
  initial_delay_seconds: conint(ge=0, le=36000) = Field(
1412
1424
  0,
1413
- description="+usage=Number of seconds after the container is started before the first probe is initiated.",
1425
+ description="+usage=Time to wait after container has started before checking the endpoint",
1414
1426
  )
1415
1427
  period_seconds: conint(ge=1, le=36000) = Field(
1416
- 10, description="+usage=How often, in seconds, to execute the probe."
1428
+ 10, description="+usage=How often to check the endpoint"
1417
1429
  )
1418
1430
  timeout_seconds: conint(ge=1, le=36000) = Field(
1419
- 1, description="+usage=Number of seconds after which the probe times out."
1431
+ 1,
1432
+ description="+usage=Time to wait for a response from the endpoint before considering it down",
1420
1433
  )
1421
1434
  success_threshold: conint(ge=1, le=100) = Field(
1422
1435
  1,
1423
- description="+usage=Minimum consecutive successes for the probe to be considered successful after having failed.",
1436
+ description="+usage=Number of successful responses from the endpoint before container is considered healthy",
1424
1437
  )
1425
1438
  failure_threshold: conint(ge=1, le=100) = Field(
1426
1439
  3,
1427
- description="+usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).",
1440
+ description="+usage=Number of consecutive failures before the container is considered down",
1428
1441
  )
1429
1442
 
1430
1443
 
@@ -1636,6 +1649,9 @@ class PythonTaskConfig(BaseModel):
1636
1649
  description="+label=Environment Variables\n+usage=Configure environment variables to be injected in the task either as plain text or secrets. [Docs](https://docs.truefoundry.com/docs/env-variables)\n+icon=fa-globe\n+sort=200",
1637
1650
  )
1638
1651
  resources: Optional[Resources] = None
1652
+ service_account: Optional[str] = Field(
1653
+ None, description="+label=Service Account\n+sort=400"
1654
+ )
1639
1655
 
1640
1656
 
1641
1657
  class SSHServer(BaseWorkbenchInput):
@@ -1715,6 +1731,21 @@ class BaseService(BaseModel):
1715
1731
  readiness_probe: Optional[HealthProbe] = None
1716
1732
 
1717
1733
 
1734
+ class FlyteLaunchPlan(BaseModel):
1735
+ id: FlyteLaunchPlanID
1736
+ spec: FlyteLaunchPlanSpec
1737
+ closure: Any
1738
+
1739
+
1740
+ class FlyteTaskCustom(BaseModel):
1741
+ truefoundry: Union[PythonTaskConfig, ContainerTaskConfig]
1742
+
1743
+
1744
+ class FlyteTaskTemplate(BaseModel):
1745
+ id: FlyteTaskID
1746
+ custom: FlyteTaskCustom
1747
+
1748
+
1718
1749
  class Service(BaseService):
1719
1750
  """
1720
1751
  +docs=Describes the configuration for the service
@@ -1750,6 +1781,34 @@ class AsyncService(BaseService):
1750
1781
  sidecar: Optional[AsyncProcessorSidecar] = None
1751
1782
 
1752
1783
 
1784
+ class FlyteTask(BaseModel):
1785
+ template: FlyteTaskTemplate
1786
+ description: Optional[Any] = None
1787
+
1788
+
1789
+ class Workflow(BaseModel):
1790
+ """
1791
+ +docs=Describes the configuration for the worflow
1792
+ """
1793
+
1794
+ type: constr(regex=r"^workflow$") = Field(..., description="+value=workflow")
1795
+ name: constr(regex=r"^[a-z][a-z0-9\-]{1,30}[a-z0-9]$") = Field(
1796
+ ...,
1797
+ description="+usage=Name of the workflow\n+sort=1\n+message=3 to 32 lower case characters long alphanumeric word, may contain - in between, cannot start with a number",
1798
+ )
1799
+ source: Union[LocalSource, RemoteSource] = Field(
1800
+ ...,
1801
+ description="+docs=Source Code for the workflow, either local or remote\n+label=Source Code for your workflow\n+icon=fa-solid fa-cloud-arrow-up:#21B6A8\n+sort=200",
1802
+ )
1803
+ workflow_file_path: str = Field(
1804
+ ...,
1805
+ description="+label=Workflow File Path\n+docs=Path to the workflow file relative to the project root path\n+sort=550",
1806
+ )
1807
+ flyte_entities: Optional[List[Union[FlyteTask, FlyteWorkflow, FlyteLaunchPlan]]] = (
1808
+ Field(None, description="+label=Flyte Entities")
1809
+ )
1810
+
1811
+
1753
1812
  class ApplicationSet(BaseModel):
1754
1813
  """
1755
1814
  +docs=Describes the configuration for the application set
@@ -600,14 +600,12 @@ class ServiceFoundryServiceClient:
600
600
  def trigger_job(
601
601
  self,
602
602
  deployment_id: str,
603
- component_name: str,
604
603
  command: Optional[str] = None,
605
604
  params: Optional[Dict[str, str]] = None,
606
605
  ) -> TriggerJobResult:
607
606
  url = f"{self._api_server_url}/{VERSION_PREFIX}/jobs/trigger"
608
607
  body = {
609
608
  "deploymentId": deployment_id,
610
- "componentName": component_name,
611
609
  "input": {},
612
610
  }
613
611
  if command:
@@ -115,7 +115,6 @@ def get_job_run(
115
115
 
116
116
  def trigger_job(
117
117
  application_fqn: str,
118
- component_name: str = None,
119
118
  command: Optional[Union[str, Sequence[str]]] = None,
120
119
  params: Optional[Dict[str, str]] = None,
121
120
  ) -> TriggerJobResult:
@@ -124,7 +123,6 @@ def trigger_job(
124
123
 
125
124
  Args:
126
125
  application_fqn: Fully Qualified Name of the Deployed Job (without the version number)
127
- component_name: Name of the component to trigger the job on. Required in case of type `application`, defaults to `None`.
128
126
  command: command to run the job with, defaults to `None`. Can be a `str` or `List[str]`
129
127
  When `None`, the job is triggered with configured command at the time of deployment.
130
128
  When passed as a list, the command will be joined using `shlex.join`
@@ -178,7 +176,6 @@ def trigger_job(
178
176
  message = f"Job triggered with params {params!r}"
179
177
  result = client.trigger_job(
180
178
  deployment_id=application_info.activeDeploymentId,
181
- component_name=component_name,
182
179
  command=command_str if command_str else None,
183
180
  params=params if params else None,
184
181
  )
@@ -0,0 +1,173 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import List, Union
5
+
6
+ from flytekit.configuration import Image as FlytekitImage
7
+ from flytekit.configuration import ImageConfig, SerializationSettings
8
+ from flytekit.models.launch_plan import LaunchPlan as FlyteLaunchPlan
9
+ from flytekit.tools.repo import serialize as serialize_workflow
10
+ from flytekit.tools.translator import TaskSpec as FlyteTaskSpec
11
+ from flytekit.tools.translator import WorkflowSpec as FlyteWorkflowSpec
12
+ from google.protobuf.json_format import MessageToDict
13
+
14
+ from truefoundry.deploy.auto_gen import models as auto_gen_models
15
+ from truefoundry.deploy.lib.clients.servicefoundry_client import (
16
+ ServiceFoundryServiceClient,
17
+ )
18
+ from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
19
+ from truefoundry.deploy.lib.model.entity import Deployment
20
+ from truefoundry.deploy.v2.lib.source import (
21
+ local_source_to_remote_source,
22
+ )
23
+ from truefoundry.logger import logger
24
+ from truefoundry.pydantic_v1 import ValidationError
25
+
26
+
27
+ def _handle_code_upload_for_workflow(
28
+ workflow: auto_gen_models.Workflow, workspace_fqn: str
29
+ ) -> auto_gen_models.Workflow:
30
+ new_workflow = workflow.copy(deep=True)
31
+ new_workflow.source = local_source_to_remote_source(
32
+ local_source=workflow.source,
33
+ workspace_fqn=workspace_fqn,
34
+ component_name=workflow.name,
35
+ )
36
+ return new_workflow
37
+
38
+
39
+ # this function does validation that num_workflows = 1, this also validates task_config is passed correctly.
40
+ # This is verified by pydantic but doing it here also as error messages are not clear in pydantic
41
+ def _validate_workflow_entities(
42
+ workflow_entities: List[Union[FlyteWorkflowSpec, FlyteLaunchPlan, FlyteTaskSpec]],
43
+ ):
44
+ workflow_objs = []
45
+ launch_plans = []
46
+ tasks = []
47
+ for entity in workflow_entities:
48
+ if isinstance(entity, FlyteWorkflowSpec):
49
+ workflow_objs.append(entity)
50
+ elif isinstance(entity, FlyteLaunchPlan):
51
+ launch_plans.append(entity)
52
+ elif isinstance(entity, FlyteTaskSpec):
53
+ tasks.append(entity)
54
+ else:
55
+ raise ValueError(f"Invalid entity found in workflow: {entity}")
56
+ if len(workflow_objs) != 1:
57
+ raise ValueError(
58
+ f"Workflow file must have exactly one workflow object. Found {len(workflow_objs)}"
59
+ )
60
+ if len(launch_plans) != 1:
61
+ raise ValueError(
62
+ f"Workflow file must have exactly one launch plan. Found {len(launch_plans)}"
63
+ )
64
+
65
+ error_message_to_use_truefoundry_decorators = """Invalid task definition for task: {}, Please use valid truefoundry decorator/class and pass task_config for tasks.
66
+ You can import truefoundry task decorators using:
67
+ `from truefoundry.workflow import task, ContainerTask, map_task`
68
+ You can pass task config using `task_config` parameter in the task definition. Task config should be one of the following:
69
+ `PythonTaskConfig`, or `ContainerTaskConfig`. You can import these using:
70
+ `from truefoundry.workflow import PythonTaskConfig, ContainerTaskConfig`
71
+ """
72
+ for task in tasks:
73
+ if not task.template.custom:
74
+ raise ValueError(
75
+ error_message_to_use_truefoundry_decorators.format(
76
+ task.template.id.name
77
+ )
78
+ )
79
+ try:
80
+ auto_gen_models.FlyteTaskCustom.validate(task.template.custom)
81
+ except ValidationError:
82
+ raise ValueError(
83
+ error_message_to_use_truefoundry_decorators.format(
84
+ task.template.id.name
85
+ )
86
+ ) from None
87
+
88
+
89
+ def _get_relative_package_path_from_filepath(
90
+ project_root_path: str, filepath: str
91
+ ) -> str:
92
+ """
93
+ This function returns the relative package path from the project root path for a given file path.
94
+ e.g. if project_root_path = /home/user/project and filepath = /home/user/project/src/module.py
95
+ then the function will return src.module
96
+ """
97
+ relative_file_path = os.path.relpath(filepath, project_root_path)
98
+ path = Path(relative_file_path)
99
+ package_path = str(path.with_suffix("")).replace(os.path.sep, ".")
100
+
101
+ return package_path
102
+
103
+
104
+ def _generate_manifest_for_workflow(
105
+ workflow: auto_gen_models.Workflow,
106
+ ):
107
+ settings = SerializationSettings(
108
+ # We are adding these defaults to avoid validation errors in flyte objects
109
+ image_config=ImageConfig(default_image=FlytekitImage(name="", tag="", fqn="")),
110
+ python_interpreter=sys.executable,
111
+ )
112
+ source_absolute_path = os.path.abspath(workflow.source.project_root_path)
113
+
114
+ workflow_file_absolute_path = os.path.join(
115
+ source_absolute_path, workflow.workflow_file_path
116
+ )
117
+ if not os.path.exists(workflow_file_absolute_path):
118
+ raise FileNotFoundError(
119
+ f"Workflow file not found at {workflow_file_absolute_path}. Workflow file path should be relative to project root path."
120
+ )
121
+
122
+ package_path = _get_relative_package_path_from_filepath(
123
+ project_root_path=source_absolute_path, filepath=workflow_file_absolute_path
124
+ )
125
+
126
+ workflow_entities = serialize_workflow(
127
+ pkgs=[package_path], settings=settings, local_source_root=source_absolute_path
128
+ )
129
+ _validate_workflow_entities(workflow_entities)
130
+
131
+ workflow.flyte_entities = []
132
+ for entity in workflow_entities:
133
+ message_dict = MessageToDict(entity.to_flyte_idl())
134
+ # proto message to dict conversion converts all int to float. so we need this hack
135
+ if (
136
+ message_dict.get("template")
137
+ and message_dict["template"].get("custom")
138
+ and message_dict["template"]["custom"].get("truefoundry")
139
+ ):
140
+ parsed_model = auto_gen_models.FlyteTaskCustom.parse_obj(
141
+ message_dict["template"]["custom"]
142
+ )
143
+ message_dict["template"]["custom"]["truefoundry"] = parsed_model.truefoundry
144
+
145
+ workflow.flyte_entities.append(message_dict)
146
+
147
+ # this step is just to verify if pydantic model is still valid after adding flyte_entities
148
+ auto_gen_models.Workflow.validate({**workflow.dict()})
149
+
150
+
151
+ def deploy_workflow(
152
+ workflow: auto_gen_models.Workflow, workspace_fqn: str, wait: bool = True
153
+ ) -> Deployment:
154
+ _generate_manifest_for_workflow(workflow)
155
+ workspace_id = get_workspace_by_fqn(workspace_fqn).id
156
+
157
+ logger.info(
158
+ f"Deploying workflow {workflow.name} to workspace {workspace_fqn} ({workspace_id})"
159
+ )
160
+
161
+ workflow = _handle_code_upload_for_workflow(
162
+ workflow=workflow, workspace_fqn=workspace_fqn
163
+ )
164
+
165
+ client = ServiceFoundryServiceClient()
166
+ response = client.deploy_application(
167
+ workspace_id=workspace_id, application=workflow
168
+ )
169
+ logger.info(
170
+ "🚀 Deployment started for application '%s'. Deployment FQN is '%s'.",
171
+ workflow.name,
172
+ response.fqn,
173
+ )
@@ -3,6 +3,7 @@ from typing import Literal, Union
3
3
  from truefoundry.deploy.auto_gen import models
4
4
  from truefoundry.deploy.lib.model.entity import Deployment
5
5
  from truefoundry.deploy.v2.lib.deploy import deploy_component
6
+ from truefoundry.deploy.v2.lib.deploy_workflow import deploy_workflow
6
7
  from truefoundry.pydantic_v1 import BaseModel, Field, conint
7
8
 
8
9
 
@@ -63,10 +64,18 @@ class SSHServer(models.SSHServer, DeployablePatchedModelBase):
63
64
 
64
65
  class Application(models.Application, DeployablePatchedModelBase):
65
66
  def deploy(self, workspace_fqn: str, wait: bool = True) -> Deployment:
66
- return deploy_component(
67
- component=self.__root__, workspace_fqn=workspace_fqn, wait=wait
68
- )
67
+ if isinstance(self.__root__, models.Workflow):
68
+ return deploy_workflow(
69
+ workflow=self.__root__, workspace_fqn=workspace_fqn, wait=wait
70
+ )
71
+ else:
72
+ return deploy_component(
73
+ component=self.__root__, workspace_fqn=workspace_fqn, wait=wait
74
+ )
69
75
 
70
76
 
71
77
  class Workflow(models.Workflow, DeployablePatchedModelBase):
72
78
  type: Literal["workflow"] = "workflow"
79
+
80
+ def deploy(self, workspace_fqn: str, wait: bool = True) -> Deployment:
81
+ return deploy_workflow(workflow=self, workspace_fqn=workspace_fqn, wait=wait)
@@ -499,12 +499,12 @@ class StaticVolumeConfig(models.StaticVolumeConfig, PatchedModelBase):
499
499
 
500
500
  class PythonTaskConfig(models.PythonTaskConfig, PatchedModelBase):
501
501
  type: Literal["python-task-config"] = "python-task-config"
502
- resources: Optional[Resources] = Resources()
502
+ resources: models.Resources = Field(default_factory=models.Resources)
503
503
 
504
504
 
505
505
  class ContainerTaskConfig(models.ContainerTaskConfig, PatchedModelBase):
506
506
  type: Literal["container-task-config"] = "container-task-config"
507
- resources: Optional[Resources] = Resources()
507
+ resources: models.Resources = Field(default_factory=models.Resources)
508
508
 
509
509
 
510
510
  class TaskDockerFileBuild(models.TaskDockerFileBuild, PatchedModelBase):
@@ -0,0 +1,4 @@
1
+ try:
2
+ __version__ = "0.6.2"
3
+ except Exception:
4
+ __version__ = "NA"
@@ -0,0 +1,19 @@
1
+ try:
2
+ from flytekit import task
3
+ except ImportError:
4
+ print("To use workflows, please run 'pip install truefoundry[workflow]'.")
5
+
6
+ from flytekit import conditional
7
+ from flytekit.types.directory import FlyteDirectory
8
+
9
+ from truefoundry.deploy.v2.lib.patched_models import (
10
+ ContainerTaskConfig,
11
+ PythonTaskConfig,
12
+ TaskDockerFileBuild,
13
+ TaskPythonBuild,
14
+ )
15
+ from truefoundry.workflow.container_task import ContainerTask
16
+ from truefoundry.workflow.map_task import map_task
17
+ from truefoundry.workflow.python_task import PythonFunctionTask
18
+ from truefoundry.workflow.task import task
19
+ from truefoundry.workflow.workflow import workflow
@@ -0,0 +1,12 @@
1
+ from flytekit import ContainerTask
2
+
3
+ from truefoundry.workflow import ContainerTaskConfig
4
+
5
+
6
+ class ContainerTask(ContainerTask):
7
+ def __init__(self, name: str, task_config: ContainerTaskConfig):
8
+ super().__init__(name=name, image="", command=[])
9
+ self.tfy_task_config = task_config
10
+
11
+ def get_custom(self, settings) -> dict:
12
+ return {"truefoundry": self.tfy_task_config.dict()}
@@ -0,0 +1 @@
1
+ tfy deploy --workspace_fqn tfy-ctl-euwe1-devtest:nikhil-ws
@@ -0,0 +1,19 @@
1
+ from truefoundry.workflow import PythonTaskConfig, TaskPythonBuild, task, workflow
2
+
3
+
4
+ @task(
5
+ task_config=PythonTaskConfig(
6
+ image=TaskPythonBuild(
7
+ python_version="3.9",
8
+ pip_packages=["flytekit==1.10.3"],
9
+ )
10
+ )
11
+ )
12
+ def say_hello() -> str:
13
+ return "Hello, World!"
14
+
15
+
16
+ @workflow
17
+ def hello_world_wf() -> str:
18
+ res = say_hello()
19
+ return res