flyte 2.0.0b32__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 flyte might be problematic. Click here for more details.

Files changed (204) hide show
  1. flyte/__init__.py +108 -0
  2. flyte/_bin/__init__.py +0 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +195 -0
  5. flyte/_bin/serve.py +178 -0
  6. flyte/_build.py +26 -0
  7. flyte/_cache/__init__.py +12 -0
  8. flyte/_cache/cache.py +147 -0
  9. flyte/_cache/defaults.py +9 -0
  10. flyte/_cache/local_cache.py +216 -0
  11. flyte/_cache/policy_function_body.py +42 -0
  12. flyte/_code_bundle/__init__.py +8 -0
  13. flyte/_code_bundle/_ignore.py +121 -0
  14. flyte/_code_bundle/_packaging.py +218 -0
  15. flyte/_code_bundle/_utils.py +347 -0
  16. flyte/_code_bundle/bundle.py +266 -0
  17. flyte/_constants.py +1 -0
  18. flyte/_context.py +155 -0
  19. flyte/_custom_context.py +73 -0
  20. flyte/_debug/__init__.py +0 -0
  21. flyte/_debug/constants.py +38 -0
  22. flyte/_debug/utils.py +17 -0
  23. flyte/_debug/vscode.py +307 -0
  24. flyte/_deploy.py +408 -0
  25. flyte/_deployer.py +109 -0
  26. flyte/_doc.py +29 -0
  27. flyte/_docstring.py +32 -0
  28. flyte/_environment.py +122 -0
  29. flyte/_excepthook.py +37 -0
  30. flyte/_group.py +32 -0
  31. flyte/_hash.py +8 -0
  32. flyte/_image.py +1055 -0
  33. flyte/_initialize.py +628 -0
  34. flyte/_interface.py +119 -0
  35. flyte/_internal/__init__.py +3 -0
  36. flyte/_internal/controllers/__init__.py +129 -0
  37. flyte/_internal/controllers/_local_controller.py +239 -0
  38. flyte/_internal/controllers/_trace.py +48 -0
  39. flyte/_internal/controllers/remote/__init__.py +58 -0
  40. flyte/_internal/controllers/remote/_action.py +211 -0
  41. flyte/_internal/controllers/remote/_client.py +47 -0
  42. flyte/_internal/controllers/remote/_controller.py +583 -0
  43. flyte/_internal/controllers/remote/_core.py +465 -0
  44. flyte/_internal/controllers/remote/_informer.py +381 -0
  45. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  46. flyte/_internal/imagebuild/__init__.py +3 -0
  47. flyte/_internal/imagebuild/docker_builder.py +706 -0
  48. flyte/_internal/imagebuild/image_builder.py +277 -0
  49. flyte/_internal/imagebuild/remote_builder.py +386 -0
  50. flyte/_internal/imagebuild/utils.py +78 -0
  51. flyte/_internal/resolvers/__init__.py +0 -0
  52. flyte/_internal/resolvers/_task_module.py +21 -0
  53. flyte/_internal/resolvers/common.py +31 -0
  54. flyte/_internal/resolvers/default.py +28 -0
  55. flyte/_internal/runtime/__init__.py +0 -0
  56. flyte/_internal/runtime/convert.py +486 -0
  57. flyte/_internal/runtime/entrypoints.py +204 -0
  58. flyte/_internal/runtime/io.py +188 -0
  59. flyte/_internal/runtime/resources_serde.py +152 -0
  60. flyte/_internal/runtime/reuse.py +125 -0
  61. flyte/_internal/runtime/rusty.py +193 -0
  62. flyte/_internal/runtime/task_serde.py +362 -0
  63. flyte/_internal/runtime/taskrunner.py +209 -0
  64. flyte/_internal/runtime/trigger_serde.py +160 -0
  65. flyte/_internal/runtime/types_serde.py +54 -0
  66. flyte/_keyring/__init__.py +0 -0
  67. flyte/_keyring/file.py +115 -0
  68. flyte/_logging.py +300 -0
  69. flyte/_map.py +312 -0
  70. flyte/_module.py +72 -0
  71. flyte/_pod.py +30 -0
  72. flyte/_resources.py +473 -0
  73. flyte/_retry.py +32 -0
  74. flyte/_reusable_environment.py +102 -0
  75. flyte/_run.py +724 -0
  76. flyte/_secret.py +96 -0
  77. flyte/_task.py +550 -0
  78. flyte/_task_environment.py +316 -0
  79. flyte/_task_plugins.py +47 -0
  80. flyte/_timeout.py +47 -0
  81. flyte/_tools.py +27 -0
  82. flyte/_trace.py +119 -0
  83. flyte/_trigger.py +1000 -0
  84. flyte/_utils/__init__.py +30 -0
  85. flyte/_utils/asyn.py +121 -0
  86. flyte/_utils/async_cache.py +139 -0
  87. flyte/_utils/coro_management.py +27 -0
  88. flyte/_utils/docker_credentials.py +173 -0
  89. flyte/_utils/file_handling.py +72 -0
  90. flyte/_utils/helpers.py +134 -0
  91. flyte/_utils/lazy_module.py +54 -0
  92. flyte/_utils/module_loader.py +104 -0
  93. flyte/_utils/org_discovery.py +57 -0
  94. flyte/_utils/uv_script_parser.py +49 -0
  95. flyte/_version.py +34 -0
  96. flyte/app/__init__.py +22 -0
  97. flyte/app/_app_environment.py +157 -0
  98. flyte/app/_deploy.py +125 -0
  99. flyte/app/_input.py +160 -0
  100. flyte/app/_runtime/__init__.py +3 -0
  101. flyte/app/_runtime/app_serde.py +347 -0
  102. flyte/app/_types.py +101 -0
  103. flyte/app/extras/__init__.py +3 -0
  104. flyte/app/extras/_fastapi.py +151 -0
  105. flyte/cli/__init__.py +12 -0
  106. flyte/cli/_abort.py +28 -0
  107. flyte/cli/_build.py +114 -0
  108. flyte/cli/_common.py +468 -0
  109. flyte/cli/_create.py +371 -0
  110. flyte/cli/_delete.py +45 -0
  111. flyte/cli/_deploy.py +293 -0
  112. flyte/cli/_gen.py +176 -0
  113. flyte/cli/_get.py +370 -0
  114. flyte/cli/_option.py +33 -0
  115. flyte/cli/_params.py +554 -0
  116. flyte/cli/_plugins.py +209 -0
  117. flyte/cli/_run.py +597 -0
  118. flyte/cli/_serve.py +64 -0
  119. flyte/cli/_update.py +37 -0
  120. flyte/cli/_user.py +17 -0
  121. flyte/cli/main.py +221 -0
  122. flyte/config/__init__.py +3 -0
  123. flyte/config/_config.py +248 -0
  124. flyte/config/_internal.py +73 -0
  125. flyte/config/_reader.py +225 -0
  126. flyte/connectors/__init__.py +11 -0
  127. flyte/connectors/_connector.py +270 -0
  128. flyte/connectors/_server.py +197 -0
  129. flyte/connectors/utils.py +135 -0
  130. flyte/errors.py +243 -0
  131. flyte/extend.py +19 -0
  132. flyte/extras/__init__.py +5 -0
  133. flyte/extras/_container.py +286 -0
  134. flyte/git/__init__.py +3 -0
  135. flyte/git/_config.py +21 -0
  136. flyte/io/__init__.py +29 -0
  137. flyte/io/_dataframe/__init__.py +131 -0
  138. flyte/io/_dataframe/basic_dfs.py +223 -0
  139. flyte/io/_dataframe/dataframe.py +1026 -0
  140. flyte/io/_dir.py +910 -0
  141. flyte/io/_file.py +914 -0
  142. flyte/io/_hashing_io.py +342 -0
  143. flyte/models.py +479 -0
  144. flyte/py.typed +0 -0
  145. flyte/remote/__init__.py +35 -0
  146. flyte/remote/_action.py +738 -0
  147. flyte/remote/_app.py +57 -0
  148. flyte/remote/_client/__init__.py +0 -0
  149. flyte/remote/_client/_protocols.py +189 -0
  150. flyte/remote/_client/auth/__init__.py +12 -0
  151. flyte/remote/_client/auth/_auth_utils.py +14 -0
  152. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  153. flyte/remote/_client/auth/_authenticators/base.py +403 -0
  154. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  155. flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
  156. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  157. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  158. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  159. flyte/remote/_client/auth/_channel.py +213 -0
  160. flyte/remote/_client/auth/_client_config.py +85 -0
  161. flyte/remote/_client/auth/_default_html.py +32 -0
  162. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  163. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  164. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  165. flyte/remote/_client/auth/_keyring.py +152 -0
  166. flyte/remote/_client/auth/_token_client.py +260 -0
  167. flyte/remote/_client/auth/errors.py +16 -0
  168. flyte/remote/_client/controlplane.py +128 -0
  169. flyte/remote/_common.py +30 -0
  170. flyte/remote/_console.py +19 -0
  171. flyte/remote/_data.py +161 -0
  172. flyte/remote/_logs.py +185 -0
  173. flyte/remote/_project.py +88 -0
  174. flyte/remote/_run.py +386 -0
  175. flyte/remote/_secret.py +142 -0
  176. flyte/remote/_task.py +527 -0
  177. flyte/remote/_trigger.py +306 -0
  178. flyte/remote/_user.py +33 -0
  179. flyte/report/__init__.py +3 -0
  180. flyte/report/_report.py +182 -0
  181. flyte/report/_template.html +124 -0
  182. flyte/storage/__init__.py +36 -0
  183. flyte/storage/_config.py +237 -0
  184. flyte/storage/_parallel_reader.py +274 -0
  185. flyte/storage/_remote_fs.py +34 -0
  186. flyte/storage/_storage.py +456 -0
  187. flyte/storage/_utils.py +5 -0
  188. flyte/syncify/__init__.py +56 -0
  189. flyte/syncify/_api.py +375 -0
  190. flyte/types/__init__.py +52 -0
  191. flyte/types/_interface.py +40 -0
  192. flyte/types/_pickle.py +145 -0
  193. flyte/types/_renderer.py +162 -0
  194. flyte/types/_string_literals.py +119 -0
  195. flyte/types/_type_engine.py +2254 -0
  196. flyte/types/_utils.py +80 -0
  197. flyte-2.0.0b32.data/scripts/debug.py +38 -0
  198. flyte-2.0.0b32.data/scripts/runtime.py +195 -0
  199. flyte-2.0.0b32.dist-info/METADATA +351 -0
  200. flyte-2.0.0b32.dist-info/RECORD +204 -0
  201. flyte-2.0.0b32.dist-info/WHEEL +5 -0
  202. flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
  203. flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
  204. flyte-2.0.0b32.dist-info/top_level.txt +1 -0
flyte/app/_input.py ADDED
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import typing
5
+ from dataclasses import dataclass, field
6
+ from functools import cache, cached_property
7
+ from typing import List, Literal, Optional
8
+
9
+ from pydantic import BaseModel
10
+
11
+ if typing.TYPE_CHECKING:
12
+ import flyte.io
13
+
14
+ RUNTIME_INPUTS_FILE = "flyte-inputs.json"
15
+
16
+ InputType = Literal["file", "directory", "string"]
17
+
18
+
19
+ @dataclass
20
+ class Input:
21
+ """
22
+ Input for application.
23
+
24
+ :param name: Name of input.
25
+ :param value: Value for input.
26
+ :param env_var: Environment name to set the value in the serving environment.
27
+ :param download: When True, the input will be automatically downloaded. This
28
+ only works if the value refers to an item in a object store. i.e. `s3://...`
29
+ :param mount: If `value` is a directory, then the directory will be available
30
+ at `mount`. If `value` is a file, then the file will be downloaded into the
31
+ `mount` directory.
32
+ :param ignore_patterns: If `value` is a directory, then this is a list of glob
33
+ patterns to ignore.
34
+ """
35
+
36
+ value: str | flyte.io.File | flyte.io.Dir
37
+ name: Optional[str] = None
38
+ env_var: Optional[str] = None
39
+ download: bool = False
40
+ mount: Optional[str] = None
41
+ ignore_patterns: list[str] = field(default_factory=list)
42
+
43
+ def __post_init__(self):
44
+ import flyte.io
45
+
46
+ env_name_re = re.compile("^[_a-zA-Z][_a-zA-Z0-9]*$")
47
+
48
+ if self.env_var is not None and env_name_re.match(self.env_var) is None:
49
+ raise ValueError(f"env_var ({self.env_var}) is not a valid environment name for shells")
50
+
51
+ if not isinstance(self.value, (str, flyte.io.File, flyte.io.Dir)):
52
+ raise TypeError(f"Expected value to be of type str, file or dir, got {type(self.value)}")
53
+
54
+ if self.name is None:
55
+ self.name = "i0"
56
+
57
+
58
+ _SerializedInputType = Literal["file", "directory", "string"]
59
+
60
+
61
+ class SerializableInput(BaseModel):
62
+ """
63
+ Serializable version of Input.
64
+ """
65
+
66
+ name: str
67
+ value: str
68
+ download: bool
69
+ type: _SerializedInputType = "string"
70
+ env_var: Optional[str] = None
71
+ dest: Optional[str] = None
72
+ ignore_patterns: List[str] = field(default_factory=list)
73
+
74
+ @classmethod
75
+ def from_input(cls, inp: Input) -> "SerializableInput":
76
+ import flyte.io
77
+
78
+ # inp.name is guaranteed to be set by Input.__post_init__
79
+ assert inp.name is not None, "Input name should be set by __post_init__"
80
+
81
+ tpe: _SerializedInputType = "string"
82
+ if isinstance(inp.value, flyte.io.File):
83
+ value = inp.value.path
84
+ tpe = "file"
85
+ download = True if inp.mount is not None else inp.download
86
+ elif isinstance(inp.value, flyte.io.Dir):
87
+ value = inp.value.path
88
+ tpe = "directory"
89
+ download = True if inp.mount is not None else inp.download
90
+ else:
91
+ value = inp.value
92
+ download = False
93
+
94
+ return cls(
95
+ name=inp.name,
96
+ value=value,
97
+ type=tpe,
98
+ download=download,
99
+ env_var=inp.env_var,
100
+ dest=inp.mount,
101
+ ignore_patterns=inp.ignore_patterns,
102
+ )
103
+
104
+
105
+ class SerializableInputCollection(BaseModel):
106
+ """
107
+ Collection of inputs for application.
108
+
109
+ :param inputs: List of inputs.
110
+ """
111
+
112
+ inputs: List[SerializableInput] = field(default_factory=list)
113
+
114
+ @cached_property
115
+ def to_transport(self) -> str:
116
+ import base64
117
+ import gzip
118
+ from io import BytesIO
119
+
120
+ json_str = self.model_dump_json()
121
+ buf = BytesIO()
122
+ with gzip.GzipFile(mode="wb", fileobj=buf, mtime=0) as f:
123
+ f.write(json_str.encode("utf-8"))
124
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
125
+
126
+ @classmethod
127
+ def from_transport(cls, s: str) -> SerializableInputCollection:
128
+ import base64
129
+ import gzip
130
+
131
+ compressed_val = base64.b64decode(s.encode("utf-8"))
132
+ json_str = gzip.decompress(compressed_val).decode("utf-8")
133
+ return cls.model_validate_json(json_str)
134
+
135
+ @classmethod
136
+ def from_inputs(cls, inputs: List[Input]) -> SerializableInputCollection:
137
+ return cls(inputs=[SerializableInput.from_input(inp) for inp in inputs])
138
+
139
+
140
+ @cache
141
+ def _load_inputs() -> dict[str, str]:
142
+ """Load inputs for application or endpoint."""
143
+ import json
144
+ import os
145
+
146
+ config_file = os.getenv(RUNTIME_INPUTS_FILE)
147
+
148
+ if config_file is None:
149
+ raise ValueError("Inputs are not mounted")
150
+
151
+ with open(config_file, "r") as f:
152
+ inputs = json.load(f)
153
+
154
+ return inputs
155
+
156
+
157
+ def get_input(name: str) -> str:
158
+ """Get inputs for application or endpoint."""
159
+ inputs = _load_inputs()
160
+ return inputs[name]
@@ -0,0 +1,3 @@
1
+ from .app_serde import translate_app_env_to_idl
2
+
3
+ __all__ = ["translate_app_env_to_idl"]
@@ -0,0 +1,347 @@
1
+ """
2
+ Serialization module for AppEnvironment to AppIDL conversion.
3
+
4
+ This module provides functionality to serialize an AppEnvironment object into
5
+ the AppIDL protobuf format, using SerializationContext for configuration.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from copy import deepcopy
11
+ from typing import List, Optional, Union
12
+
13
+ from flyteidl2.app import app_definition_pb2
14
+ from flyteidl2.common import runtime_version_pb2
15
+ from flyteidl2.core import literals_pb2, tasks_pb2
16
+ from google.protobuf.duration_pb2 import Duration
17
+
18
+ import flyte
19
+ import flyte.errors
20
+ import flyte.io
21
+ from flyte._internal.runtime.resources_serde import get_proto_extended_resources, get_proto_resources
22
+ from flyte._internal.runtime.task_serde import get_security_context, lookup_image_in_cache
23
+ from flyte.app import AppEnvironment, Input, Scaling
24
+ from flyte.models import SerializationContext
25
+
26
+
27
+ def get_proto_container(
28
+ app_env: AppEnvironment,
29
+ serialization_context: SerializationContext,
30
+ ) -> tasks_pb2.Container:
31
+ """
32
+ Construct the container specification.
33
+
34
+ Args:
35
+ app_env: The app environment
36
+ serialization_context: Serialization context
37
+
38
+ Returns:
39
+ Container protobuf message
40
+ """
41
+ from flyte import Image
42
+
43
+ env = [literals_pb2.KeyValuePair(key=k, value=v) for k, v in app_env.env_vars.items()] if app_env.env_vars else None
44
+ resources = get_proto_resources(app_env.resources)
45
+
46
+ if app_env.image == "auto":
47
+ img = Image.from_debian_base()
48
+ elif isinstance(app_env.image, str):
49
+ img = Image.from_base(app_env.image)
50
+ else:
51
+ img = app_env.image
52
+
53
+ env_name = app_env.name
54
+ img_uri = lookup_image_in_cache(serialization_context, env_name, img)
55
+
56
+ p = app_env.get_port()
57
+ container_ports = [tasks_pb2.ContainerPort(container_port=p.port, name=p.name)]
58
+
59
+ return tasks_pb2.Container(
60
+ image=img_uri,
61
+ command=app_env.container_cmd(serialization_context),
62
+ args=app_env.container_args(serialization_context),
63
+ resources=resources,
64
+ ports=container_ports,
65
+ env=env,
66
+ )
67
+
68
+
69
+ def _sanitize_resource_name(resource: tasks_pb2.Resources.ResourceEntry) -> str:
70
+ """
71
+ Sanitize resource name for Kubernetes compatibility.
72
+
73
+ Args:
74
+ resource: Resource entry
75
+
76
+ Returns:
77
+ Sanitized resource name
78
+ """
79
+ return tasks_pb2.Resources.ResourceName.Name(resource.name).lower().replace("_", "-")
80
+
81
+
82
+ def _serialized_pod_spec(
83
+ app_env: AppEnvironment,
84
+ pod_template: flyte.PodTemplate,
85
+ serialization_context: SerializationContext,
86
+ ) -> dict:
87
+ """
88
+ Convert pod spec into a dict for serialization.
89
+
90
+ Args:
91
+ app_env: The app environment
92
+ pod_template: Pod template specification
93
+ serialization_context: Serialization context
94
+
95
+ Returns:
96
+ Dictionary representation of the pod spec
97
+ """
98
+ from kubernetes.client import ApiClient
99
+ from kubernetes.client.models import V1Container, V1ContainerPort, V1EnvVar, V1ResourceRequirements
100
+
101
+ pod_template = deepcopy(pod_template)
102
+
103
+ if pod_template.pod_spec is None:
104
+ return {}
105
+
106
+ if pod_template.primary_container_name != "app":
107
+ msg = "Primary container name must be 'app'"
108
+ raise ValueError(msg)
109
+
110
+ containers: list[V1Container] = pod_template.pod_spec.containers
111
+ primary_exists = any(container.name == pod_template.primary_container_name for container in containers)
112
+
113
+ if not primary_exists:
114
+ msg = "Primary container does not exist with name 'app'"
115
+ raise ValueError(msg)
116
+
117
+ final_containers = []
118
+
119
+ # Process containers
120
+ for container in containers:
121
+ img = container.image
122
+ if isinstance(img, flyte.Image):
123
+ img = lookup_image_in_cache(serialization_context, container.name, img)
124
+ container.image = img
125
+
126
+ if container.name == pod_template.primary_container_name:
127
+ container.args = app_env.container_args(serialization_context)
128
+ container.command = app_env.container_cmd(serialization_context)
129
+
130
+ limits, requests = {}, {}
131
+ resources = get_proto_resources(app_env.resources)
132
+ if resources:
133
+ for resource in resources.limits:
134
+ limits[_sanitize_resource_name(resource)] = resource.value
135
+ for resource in resources.requests:
136
+ requests[_sanitize_resource_name(resource)] = resource.value
137
+
138
+ resource_requirements = V1ResourceRequirements(limits=limits, requests=requests)
139
+
140
+ if limits or requests:
141
+ container.resources = resource_requirements
142
+
143
+ if app_env.env_vars:
144
+ container.env = [V1EnvVar(name=k, value=v) for k, v in app_env.env_vars.items()] + (container.env or [])
145
+
146
+ _port = app_env.get_port()
147
+ container.ports = [V1ContainerPort(container_port=_port.port, name=_port.name)] + (container.ports or [])
148
+
149
+ final_containers.append(container)
150
+
151
+ pod_template.pod_spec.containers = final_containers
152
+ return ApiClient().sanitize_for_serialization(pod_template.pod_spec)
153
+
154
+
155
+ def _get_k8s_pod(
156
+ app_env: AppEnvironment,
157
+ pod_template: flyte.PodTemplate,
158
+ serialization_context: SerializationContext,
159
+ ) -> tasks_pb2.K8sPod:
160
+ """
161
+ Convert pod_template into a K8sPod IDL.
162
+
163
+ Args:
164
+ app_env: The app environment
165
+ pod_template: Pod template specification
166
+ serialization_context: Serialization context
167
+
168
+ Returns:
169
+ K8sPod protobuf message
170
+ """
171
+ import json
172
+
173
+ from google.protobuf.json_format import Parse
174
+ from google.protobuf.struct_pb2 import Struct
175
+
176
+ pod_spec_dict = _serialized_pod_spec(app_env, pod_template, serialization_context)
177
+ pod_spec_idl = Parse(json.dumps(pod_spec_dict), Struct())
178
+
179
+ metadata = tasks_pb2.K8sObjectMetadata(
180
+ labels=pod_template.labels,
181
+ annotations=pod_template.annotations,
182
+ )
183
+ return tasks_pb2.K8sPod(pod_spec=pod_spec_idl, metadata=metadata)
184
+
185
+
186
+ def _get_scaling_metric(
187
+ metric: Optional[Union[Scaling.Concurrency, Scaling.RequestRate]],
188
+ ) -> Optional[app_definition_pb2.ScalingMetric]:
189
+ """
190
+ Convert scaling metric to protobuf format.
191
+
192
+ Args:
193
+ metric: Scaling metric (Concurrency or RequestRate)
194
+
195
+ Returns:
196
+ ScalingMetric protobuf message or None
197
+ """
198
+
199
+ if metric is None:
200
+ return None
201
+
202
+ if isinstance(metric, Scaling.Concurrency):
203
+ return app_definition_pb2.ScalingMetric(concurrency=app_definition_pb2.Concurrency(val=metric.val))
204
+ elif isinstance(metric, Scaling.RequestRate):
205
+ return app_definition_pb2.ScalingMetric(request_rate=app_definition_pb2.RequestRate(val=metric.val))
206
+
207
+ return None
208
+
209
+
210
+ def translate_inputs(inputs: List[Input]) -> app_definition_pb2.InputList:
211
+ """
212
+ Placeholder for translating inputs to protobuf format.
213
+
214
+ Returns:
215
+ InputList protobuf message
216
+ """
217
+ if not inputs:
218
+ return app_definition_pb2.InputList()
219
+
220
+ inputs_list = []
221
+ for input in inputs:
222
+ if isinstance(input.value, str):
223
+ inputs_list.append(app_definition_pb2.Input(name=input.name, string_value=input.value))
224
+ elif isinstance(input.value, flyte.io.File):
225
+ inputs_list.append(app_definition_pb2.Input(name=input.name, string_value=str(input.value.path)))
226
+ elif isinstance(input.value, flyte.io.Dir):
227
+ inputs_list.append(app_definition_pb2.Input(name=input.name, string_value=str(input.value.path)))
228
+ else:
229
+ raise ValueError(f"Unsupported input value type: {type(input.value)}")
230
+ return app_definition_pb2.InputList(items=inputs_list)
231
+
232
+
233
+ def translate_app_env_to_idl(
234
+ app_env: AppEnvironment,
235
+ serialization_context: SerializationContext,
236
+ desired_state: app_definition_pb2.Spec.DesiredState = app_definition_pb2.Spec.DesiredState.DESIRED_STATE_ACTIVE,
237
+ ) -> app_definition_pb2.App:
238
+ """
239
+ Translate an AppEnvironment to AppIDL protobuf format.
240
+
241
+ This is the main entry point for serializing an AppEnvironment object into
242
+ the AppIDL protobuf format.
243
+
244
+ Args:
245
+ app_env: The app environment to serialize
246
+ serialization_context: Serialization context containing org, project, domain, version, etc.
247
+ desired_state: Desired state of the app (ACTIVE, INACTIVE, etc.)
248
+
249
+ Returns:
250
+ AppIDL protobuf message
251
+ """
252
+ # Build security context
253
+ task_sec_ctx = get_security_context(app_env.secrets)
254
+ allow_anonymous = False
255
+ if not app_env.requires_auth:
256
+ allow_anonymous = True
257
+
258
+ security_context = None
259
+ if task_sec_ctx or allow_anonymous:
260
+ security_context = app_definition_pb2.SecurityContext(
261
+ run_as=task_sec_ctx.run_as if task_sec_ctx else None,
262
+ secrets=task_sec_ctx.secrets if task_sec_ctx else [],
263
+ allow_anonymous=allow_anonymous,
264
+ )
265
+
266
+ # Build autoscaling config
267
+ scaling_metric = _get_scaling_metric(app_env.scaling.metric)
268
+
269
+ dur = None
270
+ if app_env.scaling.scaledown_after:
271
+ dur = Duration()
272
+ dur.FromTimedelta(app_env.scaling.scaledown_after)
273
+
274
+ min_replicas, max_replicas = app_env.scaling.get_replicas()
275
+ autoscaling = app_definition_pb2.AutoscalingConfig(
276
+ replicas=app_definition_pb2.Replicas(min=min_replicas, max=max_replicas),
277
+ scaledown_period=dur,
278
+ scaling_metric=scaling_metric,
279
+ )
280
+
281
+ # Build spec based on image type
282
+ container = None
283
+ pod = None
284
+ if app_env.pod_template:
285
+ if isinstance(app_env.pod_template, str):
286
+ raise NotImplementedError("PodTemplate as str is not supported yet")
287
+ pod = _get_k8s_pod(
288
+ app_env,
289
+ app_env.pod_template,
290
+ serialization_context,
291
+ )
292
+ elif app_env.image:
293
+ container = get_proto_container(
294
+ app_env,
295
+ serialization_context,
296
+ )
297
+ else:
298
+ msg = "image must be a str, Image, or PodTemplate"
299
+ raise ValueError(msg)
300
+
301
+ ingress = app_definition_pb2.IngressConfig(
302
+ private=False,
303
+ )
304
+
305
+ # Build links
306
+ links = None
307
+ if app_env.links:
308
+ links = [
309
+ app_definition_pb2.Link(path=link.path, title=link.title, is_relative=link.is_relative)
310
+ for link in app_env.links
311
+ ]
312
+
313
+ # Build profile
314
+ profile = app_definition_pb2.Profile(
315
+ type=app_env.type,
316
+ short_description=app_env.description,
317
+ )
318
+
319
+ # Build the full App IDL
320
+ return app_definition_pb2.App(
321
+ metadata=app_definition_pb2.Meta(
322
+ id=app_definition_pb2.Identifier(
323
+ org=serialization_context.org,
324
+ project=serialization_context.project,
325
+ domain=serialization_context.domain,
326
+ name=app_env.name,
327
+ ),
328
+ ),
329
+ spec=app_definition_pb2.Spec(
330
+ desired_state=desired_state,
331
+ ingress=ingress,
332
+ autoscaling=autoscaling,
333
+ security_context=security_context,
334
+ cluster_pool=app_env.cluster_pool,
335
+ extended_resources=get_proto_extended_resources(app_env.resources),
336
+ runtime_metadata=runtime_version_pb2.RuntimeMetadata(
337
+ type=runtime_version_pb2.RuntimeMetadata.RuntimeType.FLYTE_SDK,
338
+ version=flyte.version(),
339
+ flavor="python",
340
+ ),
341
+ profile=profile,
342
+ links=links,
343
+ container=container,
344
+ pod=pod,
345
+ inputs=translate_inputs(app_env.inputs),
346
+ ),
347
+ )
flyte/app/_types.py ADDED
@@ -0,0 +1,101 @@
1
+ from dataclasses import dataclass
2
+ from datetime import timedelta
3
+ from typing import Optional, Tuple, Union
4
+
5
+ import rich.repr
6
+
7
+ INVALID_APP_PORTS = [8012, 8022, 8112, 9090, 9091]
8
+
9
+
10
+ @rich.repr.auto
11
+ @dataclass(frozen=True)
12
+ class Port:
13
+ port: int
14
+ name: Optional[str] = None
15
+
16
+ def __post_init__(self):
17
+ if self.port in INVALID_APP_PORTS:
18
+ invalid_ports = ", ".join(str(p) for p in INVALID_APP_PORTS)
19
+ msg = f"port {self.port} is not allowed. Please do not use ports: {invalid_ports}"
20
+ raise ValueError(msg)
21
+
22
+
23
+ @rich.repr.auto
24
+ @dataclass(frozen=True)
25
+ class Link:
26
+ path: str
27
+ title: str
28
+ is_relative: bool = False
29
+
30
+
31
+ @rich.repr.auto
32
+ @dataclass
33
+ class Scaling:
34
+ @dataclass(frozen=True)
35
+ class Concurrency:
36
+ """
37
+ Use this to specify the concurrency metric for autoscaling, i.e. the number of concurrent requests at a replica
38
+ at which to scale up.
39
+ """
40
+
41
+ val: int
42
+
43
+ def __post_init__(self):
44
+ if self.val < 1:
45
+ raise ValueError("Concurrency must be greater than or equal to 1")
46
+
47
+ @dataclass
48
+ class RequestRate:
49
+ """
50
+ Use this to specify the request rate metric for autoscaling, i.e. the number of requests per second at a replica
51
+ at which to scale up.
52
+ """
53
+
54
+ val: int
55
+
56
+ def __post_init__(self):
57
+ if self.val < 1:
58
+ raise ValueError("Request rate must be greater than or equal to 1")
59
+
60
+ replicas: Union[int, Tuple[int, int]] = (1, 1)
61
+ metric: Optional[Union[Concurrency, RequestRate]] = None
62
+ scaledown_after: int | timedelta | None = None
63
+
64
+ def __post_init__(self):
65
+ if isinstance(self.replicas, int):
66
+ if self.replicas < 0:
67
+ raise ValueError("replicas must be greater than or equal to 0")
68
+ self.replicas = (self.replicas, self.replicas)
69
+ elif isinstance(self.replicas, tuple):
70
+ if len(self.replicas) != 2:
71
+ raise ValueError("replicas tuple must be of length 2")
72
+ min_replicas, max_replicas = self.replicas
73
+ if min_replicas < 0:
74
+ raise ValueError("min_replicas must be greater than or equal to 0")
75
+ if max_replicas < 1 or max_replicas < min_replicas:
76
+ raise ValueError("max_replicas must be greater than or equal to 1 and min_replicas")
77
+ else:
78
+ raise TypeError("replicas must be an int or a tuple of two ints")
79
+
80
+ if self.metric:
81
+ if not isinstance(self.metric, (Scaling.Concurrency, Scaling.RequestRate)):
82
+ raise TypeError("metric must be an instance of Scaling.Concurrency or Scaling.RequestRate")
83
+
84
+ if self.scaledown_after:
85
+ if isinstance(self.scaledown_after, int):
86
+ self.scaledown_after = timedelta(seconds=self.scaledown_after)
87
+ elif not isinstance(self.scaledown_after, timedelta):
88
+ raise TypeError("scaledown_after must be an int or a timedelta")
89
+
90
+ def get_replicas(self) -> Tuple[int, int]:
91
+ if isinstance(self.replicas, int):
92
+ return self.replicas, self.replicas
93
+ return self.replicas
94
+
95
+
96
+ @rich.repr.auto
97
+ @dataclass
98
+ class Domain:
99
+ # SubDomain config
100
+ subdomain: Optional[str] = None
101
+ custom_domain: Optional[str] = None
@@ -0,0 +1,3 @@
1
+ from ._fastapi import FastAPIAppEnvironment
2
+
3
+ __all__ = ["FastAPIAppEnvironment"]