flyte 2.0.0b22__py3-none-any.whl → 2.0.0b30__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.
Files changed (197) hide show
  1. flyte/__init__.py +18 -2
  2. flyte/_bin/runtime.py +43 -5
  3. flyte/_cache/cache.py +4 -2
  4. flyte/_cache/local_cache.py +216 -0
  5. flyte/_code_bundle/_ignore.py +1 -1
  6. flyte/_code_bundle/_packaging.py +4 -4
  7. flyte/_code_bundle/_utils.py +14 -8
  8. flyte/_code_bundle/bundle.py +13 -5
  9. flyte/_constants.py +1 -0
  10. flyte/_context.py +4 -1
  11. flyte/_custom_context.py +73 -0
  12. flyte/_debug/constants.py +0 -1
  13. flyte/_debug/vscode.py +6 -1
  14. flyte/_deploy.py +223 -59
  15. flyte/_environment.py +5 -0
  16. flyte/_excepthook.py +1 -1
  17. flyte/_image.py +144 -82
  18. flyte/_initialize.py +95 -12
  19. flyte/_interface.py +2 -0
  20. flyte/_internal/controllers/_local_controller.py +65 -24
  21. flyte/_internal/controllers/_trace.py +1 -1
  22. flyte/_internal/controllers/remote/_action.py +13 -11
  23. flyte/_internal/controllers/remote/_client.py +1 -1
  24. flyte/_internal/controllers/remote/_controller.py +9 -4
  25. flyte/_internal/controllers/remote/_core.py +16 -16
  26. flyte/_internal/controllers/remote/_informer.py +4 -4
  27. flyte/_internal/controllers/remote/_service_protocol.py +7 -7
  28. flyte/_internal/imagebuild/docker_builder.py +139 -84
  29. flyte/_internal/imagebuild/image_builder.py +7 -13
  30. flyte/_internal/imagebuild/remote_builder.py +65 -13
  31. flyte/_internal/imagebuild/utils.py +51 -3
  32. flyte/_internal/resolvers/_task_module.py +5 -38
  33. flyte/_internal/resolvers/default.py +2 -2
  34. flyte/_internal/runtime/convert.py +42 -20
  35. flyte/_internal/runtime/entrypoints.py +24 -1
  36. flyte/_internal/runtime/io.py +21 -8
  37. flyte/_internal/runtime/resources_serde.py +20 -6
  38. flyte/_internal/runtime/reuse.py +1 -1
  39. flyte/_internal/runtime/rusty.py +20 -5
  40. flyte/_internal/runtime/task_serde.py +33 -27
  41. flyte/_internal/runtime/taskrunner.py +10 -1
  42. flyte/_internal/runtime/trigger_serde.py +160 -0
  43. flyte/_internal/runtime/types_serde.py +1 -1
  44. flyte/_keyring/file.py +39 -9
  45. flyte/_logging.py +79 -12
  46. flyte/_map.py +31 -12
  47. flyte/_module.py +70 -0
  48. flyte/_pod.py +2 -2
  49. flyte/_resources.py +213 -31
  50. flyte/_run.py +107 -41
  51. flyte/_task.py +66 -10
  52. flyte/_task_environment.py +96 -24
  53. flyte/_task_plugins.py +4 -2
  54. flyte/_trigger.py +1000 -0
  55. flyte/_utils/__init__.py +2 -1
  56. flyte/_utils/asyn.py +3 -1
  57. flyte/_utils/docker_credentials.py +173 -0
  58. flyte/_utils/module_loader.py +17 -2
  59. flyte/_version.py +3 -3
  60. flyte/cli/_abort.py +3 -3
  61. flyte/cli/_build.py +1 -3
  62. flyte/cli/_common.py +78 -7
  63. flyte/cli/_create.py +178 -3
  64. flyte/cli/_delete.py +23 -1
  65. flyte/cli/_deploy.py +49 -11
  66. flyte/cli/_get.py +79 -34
  67. flyte/cli/_params.py +8 -6
  68. flyte/cli/_plugins.py +209 -0
  69. flyte/cli/_run.py +127 -11
  70. flyte/cli/_serve.py +64 -0
  71. flyte/cli/_update.py +37 -0
  72. flyte/cli/_user.py +17 -0
  73. flyte/cli/main.py +30 -4
  74. flyte/config/_config.py +2 -0
  75. flyte/config/_internal.py +1 -0
  76. flyte/config/_reader.py +3 -3
  77. flyte/connectors/__init__.py +11 -0
  78. flyte/connectors/_connector.py +270 -0
  79. flyte/connectors/_server.py +197 -0
  80. flyte/connectors/utils.py +135 -0
  81. flyte/errors.py +10 -1
  82. flyte/extend.py +8 -1
  83. flyte/extras/_container.py +6 -1
  84. flyte/git/_config.py +11 -9
  85. flyte/io/__init__.py +2 -0
  86. flyte/io/_dataframe/__init__.py +2 -0
  87. flyte/io/_dataframe/basic_dfs.py +1 -1
  88. flyte/io/_dataframe/dataframe.py +12 -8
  89. flyte/io/_dir.py +551 -120
  90. flyte/io/_file.py +538 -141
  91. flyte/models.py +57 -12
  92. flyte/remote/__init__.py +6 -1
  93. flyte/remote/_action.py +18 -16
  94. flyte/remote/_client/_protocols.py +39 -4
  95. flyte/remote/_client/auth/_channel.py +10 -6
  96. flyte/remote/_client/controlplane.py +17 -5
  97. flyte/remote/_console.py +3 -2
  98. flyte/remote/_data.py +4 -3
  99. flyte/remote/_logs.py +3 -3
  100. flyte/remote/_run.py +47 -7
  101. flyte/remote/_secret.py +26 -17
  102. flyte/remote/_task.py +21 -9
  103. flyte/remote/_trigger.py +306 -0
  104. flyte/remote/_user.py +33 -0
  105. flyte/storage/__init__.py +6 -1
  106. flyte/storage/_parallel_reader.py +274 -0
  107. flyte/storage/_storage.py +185 -103
  108. flyte/types/__init__.py +16 -0
  109. flyte/types/_interface.py +2 -2
  110. flyte/types/_pickle.py +17 -4
  111. flyte/types/_string_literals.py +8 -9
  112. flyte/types/_type_engine.py +26 -19
  113. flyte/types/_utils.py +1 -1
  114. {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/runtime.py +43 -5
  115. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/METADATA +8 -1
  116. flyte-2.0.0b30.dist-info/RECORD +192 -0
  117. flyte/_protos/__init__.py +0 -0
  118. flyte/_protos/common/authorization_pb2.py +0 -66
  119. flyte/_protos/common/authorization_pb2.pyi +0 -108
  120. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  121. flyte/_protos/common/identifier_pb2.py +0 -99
  122. flyte/_protos/common/identifier_pb2.pyi +0 -120
  123. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  124. flyte/_protos/common/identity_pb2.py +0 -48
  125. flyte/_protos/common/identity_pb2.pyi +0 -72
  126. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  127. flyte/_protos/common/list_pb2.py +0 -36
  128. flyte/_protos/common/list_pb2.pyi +0 -71
  129. flyte/_protos/common/list_pb2_grpc.py +0 -4
  130. flyte/_protos/common/policy_pb2.py +0 -37
  131. flyte/_protos/common/policy_pb2.pyi +0 -27
  132. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  133. flyte/_protos/common/role_pb2.py +0 -37
  134. flyte/_protos/common/role_pb2.pyi +0 -53
  135. flyte/_protos/common/role_pb2_grpc.py +0 -4
  136. flyte/_protos/common/runtime_version_pb2.py +0 -28
  137. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  138. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  139. flyte/_protos/imagebuilder/definition_pb2.py +0 -60
  140. flyte/_protos/imagebuilder/definition_pb2.pyi +0 -153
  141. flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
  142. flyte/_protos/imagebuilder/payload_pb2.py +0 -32
  143. flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
  144. flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
  145. flyte/_protos/imagebuilder/service_pb2.py +0 -29
  146. flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
  147. flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
  148. flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
  149. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
  150. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  151. flyte/_protos/secret/definition_pb2.py +0 -49
  152. flyte/_protos/secret/definition_pb2.pyi +0 -93
  153. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  154. flyte/_protos/secret/payload_pb2.py +0 -62
  155. flyte/_protos/secret/payload_pb2.pyi +0 -94
  156. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  157. flyte/_protos/secret/secret_pb2.py +0 -38
  158. flyte/_protos/secret/secret_pb2.pyi +0 -6
  159. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  160. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  161. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  162. flyte/_protos/workflow/common_pb2.py +0 -27
  163. flyte/_protos/workflow/common_pb2.pyi +0 -14
  164. flyte/_protos/workflow/common_pb2_grpc.py +0 -4
  165. flyte/_protos/workflow/environment_pb2.py +0 -29
  166. flyte/_protos/workflow/environment_pb2.pyi +0 -12
  167. flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
  168. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  169. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  170. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  171. flyte/_protos/workflow/queue_service_pb2.py +0 -111
  172. flyte/_protos/workflow/queue_service_pb2.pyi +0 -168
  173. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  174. flyte/_protos/workflow/run_definition_pb2.py +0 -123
  175. flyte/_protos/workflow/run_definition_pb2.pyi +0 -352
  176. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  177. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  178. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  179. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  180. flyte/_protos/workflow/run_service_pb2.py +0 -137
  181. flyte/_protos/workflow/run_service_pb2.pyi +0 -185
  182. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
  183. flyte/_protos/workflow/state_service_pb2.py +0 -67
  184. flyte/_protos/workflow/state_service_pb2.pyi +0 -76
  185. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  186. flyte/_protos/workflow/task_definition_pb2.py +0 -82
  187. flyte/_protos/workflow/task_definition_pb2.pyi +0 -88
  188. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  189. flyte/_protos/workflow/task_service_pb2.py +0 -60
  190. flyte/_protos/workflow/task_service_pb2.pyi +0 -59
  191. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
  192. flyte-2.0.0b22.dist-info/RECORD +0 -250
  193. {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/debug.py +0 -0
  194. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
  195. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +0 -0
  196. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
  197. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
flyte/_deploy.py CHANGED
@@ -2,10 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import hashlib
5
- import sys
6
- import typing
7
5
  from dataclasses import dataclass
8
- from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
6
+ from typing import TYPE_CHECKING, Dict, List, Optional, Protocol, Set, Tuple, Type
9
7
 
10
8
  import cloudpickle
11
9
  import rich.repr
@@ -16,13 +14,14 @@ from flyte.syncify import syncify
16
14
 
17
15
  from ._environment import Environment
18
16
  from ._image import Image
19
- from ._initialize import ensure_client, get_client, get_common_config, requires_initialization
17
+ from ._initialize import ensure_client, get_client, get_init_config, requires_initialization
20
18
  from ._logging import logger
21
19
  from ._task import TaskTemplate
22
20
  from ._task_environment import TaskEnvironment
23
21
 
24
22
  if TYPE_CHECKING:
25
- from flyte._protos.workflow import task_definition_pb2
23
+ from flyteidl2.task import task_definition_pb2
24
+ from flyteidl2.trigger import trigger_definition_pb2
26
25
 
27
26
  from ._code_bundle import CopyFiles
28
27
  from ._internal.imagebuild.image_builder import ImageCache
@@ -37,68 +36,126 @@ class DeploymentPlan:
37
36
 
38
37
  @rich.repr.auto
39
38
  @dataclass
39
+ class DeploymentContext:
40
+ """
41
+ Context for deployment operations.
42
+ """
43
+
44
+ environment: Environment | TaskEnvironment
45
+ serialization_context: SerializationContext
46
+ dryrun: bool = False
47
+
48
+
49
+ @rich.repr.auto
50
+ @dataclass
51
+ class DeployedTask:
52
+ deployed_task: task_definition_pb2.TaskSpec
53
+ deployed_triggers: List[trigger_definition_pb2.TaskTrigger]
54
+
55
+ def summary_repr(self) -> str:
56
+ """
57
+ Returns a summary representation of the deployed task.
58
+ """
59
+ return (
60
+ f"DeployedTask(name={self.deployed_task.task_template.id.name}, "
61
+ f"version={self.deployed_task.task_template.id.version})"
62
+ )
63
+
64
+ def table_repr(self) -> List[Tuple[str, ...]]:
65
+ """
66
+ Returns a table representation of the deployed task.
67
+ """
68
+ return [
69
+ ("name", self.deployed_task.task_template.id.name),
70
+ ("version", self.deployed_task.task_template.id.version),
71
+ ("triggers", ",".join([t.name for t in self.deployed_triggers])),
72
+ ]
73
+
74
+
75
+ @rich.repr.auto
76
+ @dataclass
77
+ class DeployedEnv:
78
+ env: Environment
79
+ deployed_entities: List[DeployedTask]
80
+
81
+ def summary_repr(self) -> str:
82
+ """
83
+ Returns a summary representation of the deployment.
84
+ """
85
+ entities = ", ".join(f"{e.summary_repr()}" for e in self.deployed_entities or [])
86
+ return f"Deployment(env=[{self.env.name}], entities=[{entities}])"
87
+
88
+ def table_repr(self) -> List[List[Tuple[str, ...]]]:
89
+ """
90
+ Returns a detailed representation of the deployed tasks.
91
+ """
92
+ tuples = []
93
+ if self.deployed_entities:
94
+ for e in self.deployed_entities:
95
+ tuples.append(e.table_repr())
96
+ return tuples
97
+
98
+ def env_repr(self) -> List[Tuple[str, ...]]:
99
+ """
100
+ Returns a detailed representation of the deployed environments.
101
+ """
102
+ env = self.env
103
+ return [
104
+ ("environment", env.name),
105
+ ("image", env.image.uri if isinstance(env.image, Image) else env.image or ""),
106
+ ]
107
+
108
+
109
+ @rich.repr.auto
110
+ @dataclass(frozen=True)
40
111
  class Deployment:
41
- envs: Dict[str, Environment]
42
- deployed_tasks: List[task_definition_pb2.TaskSpec] | None = None
112
+ envs: Dict[str, DeployedEnv]
43
113
 
44
114
  def summary_repr(self) -> str:
45
115
  """
46
116
  Returns a summary representation of the deployment.
47
117
  """
48
- env_names = ", ".join(self.envs.keys())
49
- task_names_versions = ", ".join(
50
- f"{task.task_template.id.name} (v{task.task_template.id.version})" for task in self.deployed_tasks or []
51
- )
52
- return f"Deployment(envs=[{env_names}], tasks=[{task_names_versions}])"
118
+ envs = ", ".join(f"{e.summary_repr()}" for e in self.envs.values() or [])
119
+ return f"Deployment(envs=[{envs}])"
53
120
 
54
- def task_repr(self) -> List[List[Tuple[str, str]]]:
121
+ def table_repr(self) -> List[List[Tuple[str, ...]]]:
55
122
  """
56
123
  Returns a detailed representation of the deployed tasks.
57
124
  """
58
125
  tuples = []
59
- if self.deployed_tasks:
60
- for task in self.deployed_tasks:
61
- tuples.append(
62
- [
63
- ("name", task.task_template.id.name),
64
- ("version", task.task_template.id.version),
65
- ]
66
- )
126
+ for d in self.envs.values():
127
+ tuples.extend(d.table_repr())
67
128
  return tuples
68
129
 
69
- def env_repr(self) -> List[List[Tuple[str, str]]]:
130
+ def env_repr(self) -> List[List[Tuple[str, ...]]]:
70
131
  """
71
132
  Returns a detailed representation of the deployed environments.
72
133
  """
73
134
  tuples = []
74
- for env_name, env in self.envs.items():
75
- tuples.append(
76
- [
77
- ("environment", env_name),
78
- ("image", env.image.uri if isinstance(env.image, Image) else env.image or ""),
79
- ]
80
- )
135
+ for d in self.envs.values():
136
+ tuples.append(d.env_repr())
81
137
  return tuples
82
138
 
83
139
 
84
140
  async def _deploy_task(
85
141
  task: TaskTemplate, serialization_context: SerializationContext, dryrun: bool = False
86
- ) -> task_definition_pb2.TaskSpec:
142
+ ) -> DeployedTask:
87
143
  """
88
144
  Deploy the given task.
89
145
  """
90
146
  ensure_client()
91
147
  import grpc.aio
148
+ from flyteidl2.task import task_definition_pb2, task_service_pb2
92
149
 
93
150
  from ._internal.runtime.convert import convert_upload_default_inputs
94
151
  from ._internal.runtime.task_serde import translate_task_to_wire
95
- from ._protos.workflow import task_definition_pb2, task_service_pb2
152
+ from ._internal.runtime.trigger_serde import to_task_trigger
96
153
 
97
154
  image_uri = task.image.uri if isinstance(task.image, Image) else task.image
98
155
 
99
156
  try:
100
157
  if dryrun:
101
- return translate_task_to_wire(task, serialization_context)
158
+ return DeployedTask(translate_task_to_wire(task, serialization_context), [])
102
159
 
103
160
  default_inputs = await convert_upload_default_inputs(task.interface)
104
161
  spec = translate_task_to_wire(task, serialization_context, default_inputs=default_inputs)
@@ -115,15 +172,31 @@ async def _deploy_task(
115
172
  name=spec.task_template.id.name,
116
173
  )
117
174
 
175
+ deployable_triggers_coros = []
176
+ for t in task.triggers:
177
+ inputs = spec.task_template.interface.inputs
178
+ default_inputs = spec.default_inputs
179
+ deployable_triggers_coros.append(
180
+ to_task_trigger(t=t, task_name=task.name, task_inputs=inputs, task_default_inputs=list(default_inputs))
181
+ )
182
+
183
+ deployable_triggers = await asyncio.gather(*deployable_triggers_coros)
118
184
  try:
119
- await get_client().task_service.DeployTask(task_service_pb2.DeployTaskRequest(task_id=task_id, spec=spec))
185
+ await get_client().task_service.DeployTask(
186
+ task_service_pb2.DeployTaskRequest(
187
+ task_id=task_id,
188
+ spec=spec,
189
+ triggers=deployable_triggers,
190
+ )
191
+ )
120
192
  logger.info(f"Deployed task {task.name} with version {task_id.version}")
121
193
  except grpc.aio.AioRpcError as e:
122
194
  if e.code() == grpc.StatusCode.ALREADY_EXISTS:
123
195
  logger.info(f"Task {task.name} with image {image_uri} already exists, skipping deployment.")
124
- return spec
196
+ return DeployedTask(spec, deployable_triggers)
125
197
  raise
126
- return spec
198
+
199
+ return DeployedTask(spec, deployable_triggers)
127
200
  except Exception as e:
128
201
  logger.error(f"Failed to deploy task {task.name} with image {image_uri}: {e}")
129
202
  raise flyte.errors.DeploymentError(
@@ -141,44 +214,128 @@ async def _build_image_bg(env_name: str, image: Image) -> Tuple[str, str]:
141
214
  return env_name, await build.aio(image)
142
215
 
143
216
 
144
- async def _build_images(deployment: DeploymentPlan) -> ImageCache:
217
+ async def _build_images(deployment: DeploymentPlan, image_refs: Dict[str, str] | None = None) -> ImageCache:
145
218
  """
146
219
  Build the images for the given deployment plan and update the environment with the built image.
147
220
  """
148
221
  from ._internal.imagebuild.image_builder import ImageCache
149
222
 
223
+ if image_refs is None:
224
+ image_refs = {}
225
+
150
226
  images = []
151
227
  image_identifier_map = {}
152
228
  for env_name, env in deployment.envs.items():
153
229
  if not isinstance(env.image, str):
230
+ if env.image._ref_name is not None:
231
+ if env.image._ref_name in image_refs:
232
+ # If the image is set in the config, set it as the base_image
233
+ image_uri = image_refs[env.image._ref_name]
234
+ env.image = env.image.clone(base_image=image_uri)
235
+ else:
236
+ raise ValueError(
237
+ f"Image name '{env.image._ref_name}' not found in config. Available: {list(image_refs.keys())}"
238
+ )
239
+ if not env.image._layers:
240
+ # No additional layers, use the base_image directly without building
241
+ image_identifier_map[env_name] = image_uri
242
+ continue
154
243
  logger.debug(f"Building Image for environment {env_name}, image: {env.image}")
155
244
  images.append(_build_image_bg(env_name, env.image))
156
245
 
157
246
  elif env.image == "auto" and "auto" not in image_identifier_map:
247
+ if "default" in image_refs:
248
+ # If the default image is set through CLI, use it instead
249
+ image_uri = image_refs["default"]
250
+ image_identifier_map[env_name] = image_uri
251
+ continue
158
252
  auto_image = Image.from_debian_base()
159
253
  images.append(_build_image_bg(env_name, auto_image))
160
254
  final_images = await asyncio.gather(*images)
161
255
 
162
256
  for env_name, image_uri in final_images:
163
257
  logger.warning(f"Built Image for environment {env_name}, image: {image_uri}")
164
- env = deployment.envs[env_name]
165
- if isinstance(env.image, Image):
166
- py_version = "{}.{}".format(*env.image.python_version)
167
- image_identifier_map[env.image.identifier] = {py_version: image_uri}
168
- elif env.image == "auto":
169
- py_version = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
170
- image_identifier_map["auto"] = {py_version: image_uri}
258
+ image_identifier_map[env_name] = image_uri
171
259
 
172
260
  return ImageCache(image_lookup=image_identifier_map)
173
261
 
174
262
 
263
+ class Deployer(Protocol):
264
+ """
265
+ Protocol for deployment callables.
266
+ """
267
+
268
+ async def __call__(self, context: DeploymentContext) -> DeployedEnv:
269
+ """
270
+ Deploy the environment described in the context.
271
+
272
+ Args:
273
+ context: Deployment context containing environment, serialization context, and dryrun flag
274
+
275
+ Returns:
276
+ Deployment result
277
+ """
278
+ ...
279
+
280
+
281
+ async def _deploy_task_env(context: DeploymentContext) -> DeployedEnv:
282
+ """
283
+ Deploy the given task environment.
284
+ """
285
+ ensure_client()
286
+ env = context.environment
287
+ if not isinstance(env, TaskEnvironment):
288
+ raise ValueError(f"Expected TaskEnvironment, got {type(env)}")
289
+
290
+ task_coros = []
291
+ for task in env.tasks.values():
292
+ task_coros.append(_deploy_task(task, context.serialization_context, dryrun=context.dryrun))
293
+ deployed_task_vals = await asyncio.gather(*task_coros)
294
+ deployed_tasks = []
295
+ for t in deployed_task_vals:
296
+ deployed_tasks.append(t)
297
+ return DeployedEnv(env=env, deployed_entities=deployed_tasks)
298
+
299
+
300
+ _ENVTYPE_REGISTRY: Dict[Type[Environment | TaskEnvironment], Deployer] = {
301
+ TaskEnvironment: _deploy_task_env,
302
+ }
303
+
304
+
305
+ def register_deployer(env_type: Type[Environment | TaskEnvironment], deployer: Deployer) -> None:
306
+ """
307
+ Register a deployer for a specific environment type.
308
+
309
+ Args:
310
+ env_type: Type of environment this deployer handles
311
+ deployer: Deployment callable that conforms to the Deployer protocol
312
+ """
313
+ _ENVTYPE_REGISTRY[env_type] = deployer
314
+
315
+
316
+ def get_deployer(env_type: Type[Environment | TaskEnvironment]) -> Deployer:
317
+ """
318
+ Get the registered deployer for an environment type.
319
+
320
+ Args:
321
+ env_type: Type of environment to get deployer for
322
+
323
+ Returns:
324
+ Deployer for the environment type, defaults to task environment deployer
325
+ """
326
+ v = _ENVTYPE_REGISTRY.get(env_type)
327
+ if v is None:
328
+ raise ValueError(f"No deployer registered for environment type {env_type}")
329
+ return v
330
+
331
+
175
332
  @requires_initialization
176
333
  async def apply(deployment_plan: DeploymentPlan, copy_style: CopyFiles, dryrun: bool = False) -> Deployment:
177
334
  from ._code_bundle import build_code_bundle
178
335
 
179
- cfg = get_common_config()
336
+ cfg = get_init_config()
180
337
 
181
- image_cache = await _build_images(deployment_plan)
338
+ image_cache = await _build_images(deployment_plan, cfg.images)
182
339
 
183
340
  if copy_style == "none" and not deployment_plan.version:
184
341
  raise flyte.errors.DeploymentError("Version must be set when copy_style is none")
@@ -203,15 +360,18 @@ async def apply(deployment_plan: DeploymentPlan, copy_style: CopyFiles, dryrun:
203
360
  root_dir=cfg.root_dir,
204
361
  )
205
362
 
206
- tasks = []
207
-
363
+ deployment_coros = []
208
364
  for env_name, env in deployment_plan.envs.items():
209
365
  logger.info(f"Deploying environment {env_name}")
210
- # TODO Make this pluggable based on the environment type
211
- if isinstance(env, TaskEnvironment):
212
- for task in env.tasks.values():
213
- tasks.append(_deploy_task(task, dryrun=dryrun, serialization_context=sc))
214
- return Deployment(envs=deployment_plan.envs, deployed_tasks=await asyncio.gather(*tasks))
366
+ deployer = get_deployer(type(env))
367
+ context = DeploymentContext(environment=env, serialization_context=sc, dryrun=dryrun)
368
+ deployment_coros.append(deployer(context))
369
+ deployed_envs = await asyncio.gather(*deployment_coros)
370
+ envs = {}
371
+ for d in deployed_envs:
372
+ envs[d.env.name] = d
373
+
374
+ return Deployment(envs)
215
375
 
216
376
 
217
377
  def _recursive_discover(planned_envs: Dict[str, Environment], env: Environment) -> Dict[str, Environment]:
@@ -219,14 +379,16 @@ def _recursive_discover(planned_envs: Dict[str, Environment], env: Environment)
219
379
  Recursively deploy the environment and its dependencies, if not already deployed (present in env_tasks) and
220
380
  return the updated env_tasks.
221
381
  """
222
- # Skip if the environment is already planned
223
382
  if env.name in planned_envs:
224
- return planned_envs
383
+ if planned_envs[env.name] is not env:
384
+ # Raise error if different TaskEnvironment objects have the same name
385
+ raise ValueError(f"Duplicate environment name '{env.name}' found")
386
+ # Add the environment to the existing envs
387
+ planned_envs[env.name] = env
388
+
225
389
  # Recursively discover dependent environments
226
390
  for dependent_env in env.depends_on:
227
391
  _recursive_discover(planned_envs, dependent_env)
228
- # Add the environment to the existing envs
229
- planned_envs[env.name] = env
230
392
  return planned_envs
231
393
 
232
394
 
@@ -234,10 +396,10 @@ def plan_deploy(*envs: Environment, version: Optional[str] = None) -> List[Deplo
234
396
  if envs is None:
235
397
  return [DeploymentPlan({})]
236
398
  deployment_plans = []
237
- visited_envs: typing.Set[str] = set()
399
+ visited_envs: Set[str] = set()
238
400
  for env in envs:
239
401
  if env.name in visited_envs:
240
- continue
402
+ raise ValueError(f"Duplicate environment name '{env.name}' found")
241
403
  planned_envs = _recursive_discover({}, env)
242
404
  deployment_plans.append(DeploymentPlan(planned_envs, version=version))
243
405
  visited_envs.update(planned_envs.keys())
@@ -281,5 +443,7 @@ async def build_images(envs: Environment) -> ImageCache:
281
443
  :param envs: Environment to build images for.
282
444
  :return: ImageCache containing the built images.
283
445
  """
446
+ cfg = get_init_config()
447
+ images = cfg.images if cfg else {}
284
448
  deployment = plan_deploy(envs)
285
- return await _build_images(deployment[0])
449
+ return await _build_images(deployment[0], images)
flyte/_environment.py CHANGED
@@ -36,6 +36,9 @@ class Environment:
36
36
  :param resources: Resources to allocate for the environment.
37
37
  :param env_vars: Environment variables to set for the environment.
38
38
  :param secrets: Secrets to inject into the environment.
39
+ :param pod_template: Pod template to use for the environment.
40
+ :param description: Description of the environment.
41
+ :param interruptible: Whether the environment is interruptible and can be scheduled on spot/preemptible instances
39
42
  :param depends_on: Environment dependencies to hint, so when you deploy the environment, the dependencies are
40
43
  also deployed. This is useful when you have a set of environments that depend on each other.
41
44
  """
@@ -47,6 +50,7 @@ class Environment:
47
50
  secrets: Optional[SecretRequest] = None
48
51
  env_vars: Optional[Dict[str, str]] = None
49
52
  resources: Optional[Resources] = None
53
+ interruptible: bool = False
50
54
  image: Union[str, Image, Literal["auto"]] = "auto"
51
55
 
52
56
  def __post_init__(self):
@@ -87,6 +91,7 @@ class Environment:
87
91
  env_vars: Optional[Dict[str, str]] = None,
88
92
  secrets: Optional[SecretRequest] = None,
89
93
  depends_on: Optional[List[Environment]] = None,
94
+ description: Optional[str] = None,
90
95
  **kwargs: Any,
91
96
  ) -> Environment:
92
97
  raise NotImplementedError
flyte/_excepthook.py CHANGED
@@ -33,5 +33,5 @@ def custom_excepthook(exc_type, exc_value, exc_tb):
33
33
  filtered_tb = [frame for frame in tb_list if should_include_frame(frame)]
34
34
  # Print the filtered version (custom format)
35
35
  print("Filtered traceback (most recent call last):")
36
- print("".join(traceback.format_list(filtered_tb)))
36
+ traceback.print_list(filtered_tb)
37
37
  print(f"{exc_type.__name__}: {exc_value}\n")