truefoundry 0.3.0rc7__py3-none-any.whl → 0.3.0rc9__py3-none-any.whl

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

Potentially problematic release.


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

@@ -6,6 +6,7 @@ from truefoundry.deploy.auto_gen.models import (
6
6
  Kustomize,
7
7
  ParamType,
8
8
  Protocol,
9
+ WorkbenchImage,
9
10
  )
10
11
  from truefoundry.deploy.lib.dao.application import (
11
12
  delete_application,
@@ -56,14 +57,10 @@ from truefoundry.deploy.v2.lib.patched_models import (
56
57
  BlueGreen,
57
58
  Build,
58
59
  Canary,
59
- CodeserverImage,
60
60
  CoreNATSOutputConfig,
61
61
  CPUUtilizationMetric,
62
62
  CronMetric,
63
63
  CUDAVersion,
64
- CustomCodeserverImage,
65
- CustomNotebookImage,
66
- CustomSSHServerImage,
67
64
  DockerFileBuild,
68
65
  DynamicVolumeConfig,
69
66
  Endpoint,
@@ -104,15 +101,10 @@ from truefoundry.deploy.v2.lib.patched_models import (
104
101
  SQSInputConfig,
105
102
  SQSOutputConfig,
106
103
  SQSQueueMetricConfig,
107
- SSHServerImage,
108
104
  StaticVolumeConfig,
109
105
  StringDataMount,
110
106
  TPUType,
111
107
  TruefoundryArtifactSource,
112
- TruefoundryImageBase,
113
- TruefoundryImageCuda1180,
114
- TruefoundryImageCuda1211,
115
- TruefoundryImageFull,
116
108
  VolumeBrowser,
117
109
  VolumeMount,
118
110
  WorkerConfig,
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: application.json
3
- # timestamp: 2024-07-19T06:56:20+00:00
3
+ # timestamp: 2024-08-09T07:42:58+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -125,6 +125,13 @@ class AsyncProcessorSidecar(BaseModel):
125
125
  )
126
126
 
127
127
 
128
+ class Autoshutdown(BaseModel):
129
+ wait_time: conint(ge=0) = Field(
130
+ 300,
131
+ description="+label=Wait Time\n+usage=The period to wait after the last received request before scaling the replicas to 0",
132
+ )
133
+
134
+
128
135
  class BaseAutoscaling(BaseModel):
129
136
  min_replicas: conint(ge=0) = Field(
130
137
  1,
@@ -138,10 +145,6 @@ class BaseAutoscaling(BaseModel):
138
145
  30,
139
146
  description="+label=Polling Interval\n+usage=This is the interval to check each trigger on.",
140
147
  )
141
- cooldown_period: conint(ge=0) = Field(
142
- 300,
143
- description="+label=Cooldown Period\n+usage=The period to wait after the last trigger reported active before scaling the resource back to 0.",
144
- )
145
148
 
146
149
 
147
150
  class BasicAuthCreds(BaseModel):
@@ -197,26 +200,6 @@ class CanaryStep(BaseModel):
197
200
  )
198
201
 
199
202
 
200
- class CodeserverImage(BaseModel):
201
- """
202
- +usage=Codeserver with persistent environment (Python 3.11.6)
203
- """
204
-
205
- type: constr(regex=r"^codeserver$") = Field(..., description="+value=codeserver")
206
- enable_sudo: bool = Field(
207
- True,
208
- description="+label=Enable root access to the container\n+usage=Changes made to the root directory `/` will not be persisted across notebook restarts",
209
- )
210
- apt_packages: Optional[List[str]] = Field(
211
- None,
212
- description='+label=List of Debian packages to install.\n+usage=Debian packages to install via `apt get`.\nIn Python/YAML E.g. ["git", "ffmpeg", "htop"]\n+placeholder=Enter a debian package name E.g. ffmpeg',
213
- )
214
- docker_registry: Optional[str] = Field(
215
- None,
216
- 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",
217
- )
218
-
219
-
220
203
  class CronMetric(BaseModel):
221
204
  type: Literal["cron"] = Field(..., description="+value=cron")
222
205
  desired_replicas: Optional[conint(ge=1)] = Field(
@@ -237,60 +220,6 @@ class CronMetric(BaseModel):
237
220
  )
238
221
 
239
222
 
240
- class CustomCodeserverImage(BaseModel):
241
- """
242
- +usage=User supplied docker image URI for vscode server
243
- """
244
-
245
- type: constr(regex=r"^customcodeserver$") = Field(
246
- ..., description="+value=customcodeserver"
247
- )
248
- image_uri: str = Field(
249
- ...,
250
- description="+label=Image URI\n+usage=The image URI. Specify the name of the image and the tag.\nIf the image is in Dockerhub, you can skip registry-url (for e.g. `tensorflow/tensorflow`).\nYou can use an image from a private registry using Advanced fields\n+placeholder=registry-url/account/image:version",
251
- )
252
- docker_registry: Optional[str] = Field(
253
- None,
254
- description="+docs=FQN of the container registry. You can use 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",
255
- )
256
-
257
-
258
- class CustomNotebookImage(BaseModel):
259
- """
260
- +usage=User supplied docker image URI for jupyter notebook
261
- """
262
-
263
- type: constr(regex=r"^customnotebook$") = Field(
264
- ..., description="+value=customnotebook"
265
- )
266
- image_uri: str = Field(
267
- ...,
268
- description="+label=Image URI\n+usage=The image URI. Specify the name of the image and the tag.\nIf the image is in Dockerhub, you can skip registry-url (for e.g. `tensorflow/tensorflow`).\nYou can use an image from a private registry using Advanced fields\n+placeholder=registry-url/account/image:version",
269
- )
270
- docker_registry: Optional[str] = Field(
271
- None,
272
- description="+docs=FQN of the container registry. You can use 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",
273
- )
274
-
275
-
276
- class CustomSSHServerImage(BaseModel):
277
- """
278
- +usage=User supplied docker image URI for ssh server
279
- """
280
-
281
- type: constr(regex=r"^custom-ssh-server$") = Field(
282
- ..., description="+value=custom-ssh-server"
283
- )
284
- image_uri: str = Field(
285
- ...,
286
- description="+label=Image URI\n+usage=The image URI. Specify the name of the image and the tag.\nIf the image is in Dockerhub, you can skip registry-url (for e.g. `tensorflow/tensorflow`).\nYou can use an image from a private registry using Advanced fields\n+placeholder=registry-url/account/image:version",
287
- )
288
- docker_registry: Optional[str] = Field(
289
- None,
290
- description="+docs=FQN of the container registry. You can use 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",
291
- )
292
-
293
-
294
223
  class DockerFileBuild(BaseModel):
295
224
  """
296
225
  +docs=Describes that we are using a dockerfile to build our image
@@ -976,22 +905,6 @@ class SQSQueueMetricConfig(BaseModel):
976
905
  )
977
906
 
978
907
 
979
- class SSHServerImage(BaseModel):
980
- """
981
- +usage=Ssh Server with persistent environment (Python 3.11.6)
982
- """
983
-
984
- type: constr(regex=r"^ssh-server$") = Field(..., description="+value=ssh-server")
985
- apt_packages: Optional[List[str]] = Field(
986
- None,
987
- description='+label=List of Debian packages to install.\n+usage=Debian packages to install via `apt get`.\nIn Python/YAML E.g. ["git", "ffmpeg", "htop"]\n+placeholder=Enter a debian package name E.g. ffmpeg',
988
- )
989
- docker_registry: Optional[str] = Field(
990
- None,
991
- 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",
992
- )
993
-
994
-
995
908
  class ConcurrencyPolicy(str, Enum):
996
909
  """
997
910
  +usage=Choose whether to allow this job to run while another instance of the job is running, or to replace the currently running instance. Allow
@@ -1149,86 +1062,6 @@ class TruefoundryArtifactSource(BaseModel):
1149
1062
  )
1150
1063
 
1151
1064
 
1152
- class TruefoundryImageBase(BaseModel):
1153
- """
1154
- +usage=JupyterLab with persistent python environment (Python 3.11.6)
1155
- """
1156
-
1157
- type: constr(regex=r"^truefoundrybase$") = Field(
1158
- ..., description="+value=truefoundrybase"
1159
- )
1160
- enable_sudo: bool = Field(
1161
- True,
1162
- description="+label=Enable root access to the container\n+usage=Changes made to the root directory `/` will not be persisted across notebook restarts",
1163
- )
1164
- apt_packages: Optional[List[str]] = Field(
1165
- None,
1166
- description='+label=List of Debian packages to install.\n+usage=Debian packages to install via `apt get`.\nIn Python/YAML E.g. ["git", "ffmpeg", "htop"]\n+placeholder=Enter a debian package name E.g. ffmpeg',
1167
- )
1168
- docker_registry: Optional[str] = Field(
1169
- None,
1170
- description="+docs=FQN of the container registry. You can use 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",
1171
- )
1172
-
1173
-
1174
- class TruefoundryImageCuda1180(BaseModel):
1175
- """
1176
- +usage=JupyterLab with persistent python environment (Python 3.11.6, Cuda 11.8.0)
1177
- """
1178
-
1179
- type: constr(regex=r"^truefoundrycuda1180$") = Field(
1180
- ..., description="+value=truefoundrycuda1180"
1181
- )
1182
- apt_packages: Optional[List[str]] = Field(
1183
- None,
1184
- description='+label=List of Debian packages to install.\n+usage=Debian packages to install via `apt get`.\nIn Python/YAML E.g. ["git", "ffmpeg", "htop"]\n+placeholder=Enter a debian package name E.g. ffmpeg',
1185
- )
1186
- docker_registry: Optional[str] = Field(
1187
- None,
1188
- description="+docs=FQN of the container registry. You can use 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",
1189
- )
1190
-
1191
-
1192
- class TruefoundryImageCuda1211(BaseModel):
1193
- """
1194
- +usage=JupyterLab with persistent python environment (Python 3.11.6, Cuda 12.1.1)
1195
- """
1196
-
1197
- type: constr(regex=r"^truefoundrycuda1211$") = Field(
1198
- ..., description="+value=truefoundrycuda1211"
1199
- )
1200
- apt_packages: Optional[List[str]] = Field(
1201
- None,
1202
- description='+label=List of Debian packages to install.\n+usage=Debian packages to install via `apt get`.\nIn Python/YAML E.g. ["git", "ffmpeg", "htop"]\n+placeholder=Enter a debian package name E.g. ffmpeg',
1203
- )
1204
- docker_registry: Optional[str] = Field(
1205
- None,
1206
- description="+docs=FQN of the container registry. You can use 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",
1207
- )
1208
-
1209
-
1210
- class TruefoundryImageFull(BaseModel):
1211
- """
1212
- +usage=JupyterLab + Tensorflow 2.15.0 and Pytorch 2.1.1 with persistent environment (Python 3.11.6)
1213
- """
1214
-
1215
- type: constr(regex=r"^truefoundryfull$") = Field(
1216
- ..., description="+value=truefoundryfull"
1217
- )
1218
- enable_sudo: bool = Field(
1219
- True,
1220
- description="+label=Enable root access to the container\n+usage=Changes made to the root directory `/` will not be persisted across notebook restarts",
1221
- )
1222
- apt_packages: Optional[List[str]] = Field(
1223
- None,
1224
- description='+label=List of Debian packages to install.\n+usage=Debian packages to install via `apt get`.\nIn Python/YAML E.g. ["git", "ffmpeg", "htop"]\n+placeholder=Enter a debian package name E.g. ffmpeg',
1225
- )
1226
- docker_registry: Optional[str] = Field(
1227
- None,
1228
- description="+docs=FQN of the container registry. You can use 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",
1229
- )
1230
-
1231
-
1232
1065
  class VolumeBrowser(BaseModel):
1233
1066
  """
1234
1067
  +label=Volume Browser
@@ -1265,6 +1098,25 @@ class VolumeMount(BaseModel):
1265
1098
  )
1266
1099
 
1267
1100
 
1101
+ class WorkbenchImage(BaseModel):
1102
+ """
1103
+ +usage=Workbench Image with persistent environment (Python 3.11.6)
1104
+ """
1105
+
1106
+ image_uri: str = Field(
1107
+ ...,
1108
+ description="+label=Image URI\n+usage=The image URI. Specify the name of the image and the tag.\nIf the image is in Dockerhub, you can skip registry-url (for e.g. `tensorflow/tensorflow`).\nYou can use an image from a private registry using Advanced fields\n+placeholder=registry-url/account/image:version",
1109
+ )
1110
+ build_script: Optional[constr(min_length=1, max_length=1024)] = Field(
1111
+ None,
1112
+ description="+label=Build Script\n+usage=The build script to run when building the image.\nThis will be executed as the last step in the docker build process as the root user (RUN DEBIAN_FRONTEND=noninteractive bash -ex build_script.sh)\n+placeholder=Enter the build script",
1113
+ )
1114
+ docker_registry: Optional[str] = Field(
1115
+ None,
1116
+ 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",
1117
+ )
1118
+
1119
+
1268
1120
  class ArtifactsDownload(BaseModel):
1269
1121
  """
1270
1122
  +docs=Describes the configuration for the artifacts cache
@@ -1317,6 +1169,10 @@ class BaseWorkbenchInput(BaseModel):
1317
1169
  )
1318
1170
  service_account: Optional[str] = Field(None, description="+sort=10113")
1319
1171
  kustomize: Optional[Kustomize] = None
1172
+ workspace_fqn: Optional[str] = Field(
1173
+ None,
1174
+ description="+label=Workspace FQN\n+docs=Fully qualified name of the workspace\n+uiType=Hidden",
1175
+ )
1320
1176
 
1321
1177
 
1322
1178
  class Build(BaseModel):
@@ -1361,9 +1217,7 @@ class Codeserver(BaseWorkbenchInput):
1361
1217
  """
1362
1218
 
1363
1219
  type: constr(regex=r"^codeserver$") = Field(..., description="+value=Code Server")
1364
- image: Union[CodeserverImage, CustomCodeserverImage] = Field(
1365
- ..., description="+usage=Pick a codeserver image to deploy\n+sort=2"
1366
- )
1220
+ image: WorkbenchImage
1367
1221
  auth: Optional[BasicAuthCreds] = None
1368
1222
 
1369
1223
 
@@ -1458,6 +1312,10 @@ class Helm(BaseModel):
1458
1312
  )
1459
1313
  kustomize: Optional[Kustomize] = None
1460
1314
  ignoreDifferences: Optional[List[Dict[str, Any]]] = None
1315
+ workspace_fqn: Optional[str] = Field(
1316
+ None,
1317
+ description="+label=Workspace FQN\n+docs=Fully qualified name of the workspace\n+uiType=Hidden",
1318
+ )
1461
1319
 
1462
1320
 
1463
1321
  class Job(BaseModel):
@@ -1509,6 +1367,10 @@ class Job(BaseModel):
1509
1367
  )
1510
1368
  labels: Optional[Dict[str, str]] = Field(None, description="+label=Labels")
1511
1369
  kustomize: Optional[Kustomize] = None
1370
+ workspace_fqn: Optional[str] = Field(
1371
+ None,
1372
+ description="+label=Workspace FQN\n+docs=Fully qualified name of the workspace\n+uiType=Hidden",
1373
+ )
1512
1374
 
1513
1375
 
1514
1376
  class KafkaInputConfig(BaseModel):
@@ -1615,16 +1477,7 @@ class Notebook(BaseWorkbenchInput):
1615
1477
  """
1616
1478
 
1617
1479
  type: constr(regex=r"^notebook$") = Field(..., description="+value=notebook")
1618
- image: Union[
1619
- TruefoundryImageBase,
1620
- TruefoundryImageFull,
1621
- CustomNotebookImage,
1622
- TruefoundryImageCuda1180,
1623
- TruefoundryImageCuda1211,
1624
- ] = Field(
1625
- ...,
1626
- description="+usage=Changes made to the root directory `/` will not be persisted across notebook restarts\n+sort=2",
1627
- )
1480
+ image: WorkbenchImage
1628
1481
  auth: Optional[BasicAuthCreds] = None
1629
1482
  cull_timeout: conint(ge=5) = Field(
1630
1483
  30,
@@ -1660,9 +1513,7 @@ class SSHServer(BaseWorkbenchInput):
1660
1513
  """
1661
1514
 
1662
1515
  type: constr(regex=r"^ssh-server$") = Field(..., description="+value=SSH Server")
1663
- image: Union[SSHServerImage, CustomSSHServerImage] = Field(
1664
- ..., description="+usage=Pick a ssh server image to deploy\n+sort=2"
1665
- )
1516
+ image: WorkbenchImage
1666
1517
  ssh_public_key: str = Field(
1667
1518
  ...,
1668
1519
  description="+label: SSH Public Key\n+usage=Add Your SSH Public Key, this will be used to authenticate you to the SSH Server. You can find it using `cat ~/.ssh/id_rsa.pub`\n+sort=4",
@@ -1680,6 +1531,10 @@ class Volume(BaseModel):
1680
1531
  description="+sort=2\n+label=Volume Config\n+message=Volume Configuration, can be either Dynamically provisioned or statically provisioned.",
1681
1532
  )
1682
1533
  volume_browser: Optional[VolumeBrowser] = None
1534
+ workspace_fqn: Optional[str] = Field(
1535
+ None,
1536
+ description="+label=Workspace FQN\n+docs=Fully qualified name of the workspace\n+uiType=Hidden",
1537
+ )
1683
1538
 
1684
1539
 
1685
1540
  class WorkerConfig(BaseModel):
@@ -1729,6 +1584,10 @@ class BaseService(BaseModel):
1729
1584
  kustomize: Optional[Kustomize] = None
1730
1585
  liveness_probe: Optional[HealthProbe] = None
1731
1586
  readiness_probe: Optional[HealthProbe] = None
1587
+ workspace_fqn: Optional[str] = Field(
1588
+ None,
1589
+ description="+label=Workspace FQN\n+docs=Fully qualified name of the workspace\n+uiType=Hidden",
1590
+ )
1732
1591
 
1733
1592
 
1734
1593
  class FlyteLaunchPlan(BaseModel):
@@ -1756,6 +1615,7 @@ class Service(BaseService):
1756
1615
  1,
1757
1616
  description="+label=Replicas\n+usage=Deploy multiple instances of your pods to distribute incoming traffic across them, ensuring effective load balancing.\n+icon=fa-clone\n+sort=4",
1758
1617
  )
1618
+ auto_shutdown: Optional[Autoshutdown] = None
1759
1619
  allow_interception: bool = Field(
1760
1620
  False,
1761
1621
  description="+label=Allow intercepts\n+usage=Whether to allow intercepts to be applied for this service.\nThis would inject an additional sidecar in each pod of the service. Not recommended on production",
@@ -10,19 +10,17 @@ from truefoundry.deploy.builder.builders import get_builder
10
10
  from truefoundry.deploy.builder.builders.tfy_notebook_buildpack.dockerfile_template import (
11
11
  NotebookImageBuild,
12
12
  )
13
- from truefoundry.pydantic_v1 import BaseModel
13
+ from truefoundry.pydantic_v1 import BaseModel, Field
14
14
 
15
15
 
16
16
  class _BuildConfig(BaseModel):
17
- # I cannot use Field(discriminator="build_config_type") here as
18
- # build_config_type in the build configs is not a Literal.
19
17
  __root__: Union[
20
18
  DockerFileBuild,
21
19
  PythonBuild,
22
20
  NotebookImageBuild,
23
21
  TaskPythonBuild,
24
22
  TaskDockerFileBuild,
25
- ]
23
+ ] = Field(discriminator="type")
26
24
 
27
25
 
28
26
  def build(
@@ -13,18 +13,19 @@ __all__ = ["generate_dockerfile_content", "build"]
13
13
 
14
14
 
15
15
  def _convert_to_dockerfile_build_config(
16
- build_configuration: NotebookImageBuild,
17
- dockerfile_path: str,
16
+ build_configuration: NotebookImageBuild, local_dir: str
18
17
  ) -> DockerFileBuild:
19
18
  dockerfile_content = generate_dockerfile_content(
20
- build_configuration=build_configuration
19
+ build_configuration=build_configuration,
20
+ local_dir=local_dir,
21
21
  )
22
+ dockerfile_path = os.path.join(local_dir, "Dockerfile")
22
23
  with open(dockerfile_path, "w", encoding="utf8") as fp:
23
24
  fp.write(dockerfile_content)
24
-
25
25
  return DockerFileBuild(
26
26
  type="dockerfile",
27
27
  dockerfile_path=dockerfile_path,
28
+ build_context_path=local_dir,
28
29
  )
29
30
 
30
31
 
@@ -35,7 +36,8 @@ def build(
35
36
  ):
36
37
  with TemporaryDirectory() as local_dir:
37
38
  docker_build_configuration = _convert_to_dockerfile_build_config(
38
- build_configuration, dockerfile_path=os.path.join(local_dir, "Dockerfile")
39
+ build_configuration,
40
+ local_dir=local_dir,
39
41
  )
40
42
  dockerfile.build(
41
43
  tag=tag,
@@ -1,4 +1,6 @@
1
- from typing import List, Optional
1
+ import hashlib
2
+ import os
3
+ from typing import Literal, Optional
2
4
 
3
5
  from mako.template import Template
4
6
 
@@ -6,43 +8,56 @@ from truefoundry.pydantic_v1 import BaseModel
6
8
 
7
9
 
8
10
  class NotebookImageBuild(BaseModel):
9
- type: str
11
+ type: Literal["tfy-notebook-buildpack"] = "tfy-notebook-buildpack"
10
12
  base_image_uri: str
11
- apt_packages: List[str]
13
+ build_script: Optional[str] = None
12
14
 
13
15
 
14
16
  DOCKERFILE_TEMPLATE = Template(
15
17
  """
16
18
  FROM ${base_image_uri}
17
19
  USER root
18
- RUN ${apt_install_command}
20
+
21
+ % if build_script_docker_commands is not None:
22
+ ${build_script_docker_commands}
23
+ % endif
24
+
19
25
  USER $NB_UID
20
26
  """
21
27
  )
22
28
 
23
29
 
24
- def generate_apt_install_command(apt_packages: Optional[List[str]]) -> Optional[str]:
25
- packages_list = None
26
- if apt_packages:
27
- packages_list = " ".join(p.strip() for p in apt_packages if p.strip())
28
- if not packages_list:
30
+ def generate_build_script_docker_commands(
31
+ build_script: Optional[str], local_dir: str
32
+ ) -> Optional[str]:
33
+ if not build_script:
29
34
  return None
30
- apt_update_command = "apt update"
31
- apt_install_command = f"DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends {packages_list}"
32
- clear_apt_lists_command = "rm -rf /var/lib/apt/lists/*"
33
- return " && ".join(
34
- [apt_update_command, apt_install_command, clear_apt_lists_command]
35
- )
35
+ build_script_path = None
36
+ if build_script:
37
+ # we add build script's hash to the file name to ensure docker cache invalidation
38
+ script_hash = hashlib.sha256(build_script.encode("utf-8")).hexdigest()
39
+ build_script_path = os.path.join(local_dir, f"build-script-{script_hash}.sh")
40
+ with open(build_script_path, "w") as fp:
41
+ fp.write(build_script)
42
+ build_script_path = os.path.relpath(build_script_path, local_dir)
43
+ run_build_script_command = f"""\
44
+ COPY {build_script_path} /tmp/user-build-script.sh
45
+ RUN mkdir -p /var/log/ && DEBIAN_FRONTEND=noninteractive bash -ex /tmp/user-build-script.sh 2>&1 | tee /var/log/user-build-script-output.log
46
+ """
47
+ return run_build_script_command
36
48
 
37
49
 
38
- def generate_dockerfile_content(build_configuration: NotebookImageBuild) -> str:
39
- apt_install_command = generate_apt_install_command(
40
- apt_packages=build_configuration.apt_packages
50
+ def generate_dockerfile_content(
51
+ build_configuration: NotebookImageBuild, local_dir: str
52
+ ) -> str:
53
+ build_script_docker_commands = generate_build_script_docker_commands(
54
+ build_script=build_configuration.build_script,
55
+ local_dir=local_dir,
41
56
  )
42
57
 
43
58
  template_args = {
44
59
  "base_image_uri": build_configuration.base_image_uri,
45
- "apt_install_command": apt_install_command,
60
+ "build_script_docker_commands": build_script_docker_commands,
46
61
  }
47
62
 
48
63
  template = DOCKERFILE_TEMPLATE
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ from typing import Optional
3
4
 
4
5
  import rich_click as click
5
6
  import yaml
@@ -9,6 +10,7 @@ from click.exceptions import ClickException
9
10
  from truefoundry.autodeploy.cli import cli as autodeploy_cli
10
11
  from truefoundry.autodeploy.exception import InvalidRequirementsException
11
12
  from truefoundry.deploy.cli.const import GROUP_CLS
13
+ from truefoundry.deploy.cli.util import handle_exception_wrapper
12
14
 
13
15
 
14
16
  def _get_default_spec_file():
@@ -41,8 +43,8 @@ def _get_default_spec_file():
41
43
  "-w",
42
44
  "--workspace-fqn",
43
45
  "--workspace_fqn",
44
- required=True,
45
- help="FQN of the Workspace to deploy to",
46
+ default=None,
47
+ help="FQN of the Workspace to deploy to. If not provided, the Workspace FQN will be read from the deployment spec if available.",
46
48
  )
47
49
  @click.option(
48
50
  "--wait/--no-wait",
@@ -52,7 +54,8 @@ def _get_default_spec_file():
52
54
  default=True,
53
55
  help="Wait and tail the deployment progress",
54
56
  )
55
- def deploy_command(file: str, workspace_fqn: str, wait: bool):
57
+ @handle_exception_wrapper
58
+ def deploy_command(file: str, workspace_fqn: Optional[str], wait: bool):
56
59
  from truefoundry.deploy.lib.auth.servicefoundry_session import ServiceFoundrySession
57
60
  from truefoundry.deploy.v2.lib.deployable_patched_models import Application
58
61
 
@@ -4,6 +4,7 @@ import rich_click as click
4
4
  import yaml
5
5
 
6
6
  from truefoundry.deploy.cli.const import GROUP_CLS
7
+ from truefoundry.deploy.cli.util import handle_exception_wrapper
7
8
  from truefoundry.deploy.lib.dao import application as application_lib
8
9
 
9
10
 
@@ -44,6 +45,7 @@ from truefoundry.deploy.lib.dao import application as application_lib
44
45
  default=True,
45
46
  help="Wait and tail the deployment progress",
46
47
  )
48
+ @handle_exception_wrapper
47
49
  def patch_application_command(
48
50
  patch_file: str, application_fqn: str, patch: str, wait: bool
49
51
  ):
@@ -191,10 +191,61 @@ def _warn_when_gpu_selected_without_cuda(component: Component):
191
191
  )
192
192
 
193
193
 
194
+ def _resolve_workspace_fqn(
195
+ component: Component, workspace_fqn: Optional[str] = None
196
+ ) -> str:
197
+ if not workspace_fqn:
198
+ if hasattr(component, "workspace_fqn") and component.workspace_fqn:
199
+ resolved_workspace_fqn = component.workspace_fqn
200
+ else:
201
+ raise ValueError(
202
+ f"""\
203
+ No Workspace FQN was provided or mentioned in the spec.
204
+ Either add a `workspace_fqn` to your yaml spec as
205
+
206
+ ```
207
+ name: {getattr(component, 'name', 'my-app')}
208
+ type: {getattr(component, 'type', 'undefined')}
209
+ ...
210
+ workspace_fqn: <your workspace fqn>
211
+ ```
212
+
213
+ or Python deployment spec as
214
+
215
+ ```
216
+ app = {component.__class__.__name__}(
217
+ name='{getattr(component, 'name', 'my-app')}',
218
+ ...
219
+ workspace_fqn='<your workspace fqn>'
220
+ )
221
+ ```
222
+
223
+ or pass it explicitly using `--workspace-fqn` argument on CLI.
224
+ """
225
+ )
226
+ else:
227
+ if (
228
+ hasattr(component, "workspace_fqn")
229
+ and component.workspace_fqn
230
+ and component.workspace_fqn != workspace_fqn
231
+ ):
232
+ logger.warning(
233
+ f"`workspace_fqn` set in the deployment spec doesn't match the provided `workspace_fqn` argument {component.workspace_fqn!r} \n"
234
+ f"Using `workspace_fqn`: {workspace_fqn!r} "
235
+ )
236
+ resolved_workspace_fqn = workspace_fqn
237
+
238
+ return resolved_workspace_fqn
239
+
240
+
194
241
  def deploy_component(
195
- component: Component, workspace_fqn: str, wait: bool = True
242
+ component: Component, workspace_fqn: Optional[str] = None, wait: bool = True
196
243
  ) -> Deployment:
197
244
  _warn_when_gpu_selected_without_cuda(component=component)
245
+ workspace_fqn = _resolve_workspace_fqn(
246
+ component=component, workspace_fqn=workspace_fqn
247
+ )
248
+ component.workspace_fqn = workspace_fqn
198
249
  workspace_id = get_workspace_by_fqn(workspace_fqn).id
199
250
  updated_component = _handle_if_local_source(
200
251
  component=component, workspace_fqn=workspace_fqn
@@ -1,10 +1,15 @@
1
1
  import os
2
2
  import sys
3
3
  from pathlib import Path
4
- from typing import List, Union
4
+ from typing import Dict, List, Optional, Union
5
5
 
6
+ import requirements
7
+ from flytekit.configuration import (
8
+ SERIALIZED_CONTEXT_ENV_VAR,
9
+ ImageConfig,
10
+ SerializationSettings,
11
+ )
6
12
  from flytekit.configuration import Image as FlytekitImage
7
- from flytekit.configuration import ImageConfig, SerializationSettings
8
13
  from flytekit.models.launch_plan import LaunchPlan as FlyteLaunchPlan
9
14
  from flytekit.tools.repo import serialize as serialize_workflow
10
15
  from flytekit.tools.translator import TaskSpec as FlyteTaskSpec
@@ -22,6 +27,10 @@ from truefoundry.deploy.v2.lib.source import (
22
27
  )
23
28
  from truefoundry.logger import logger
24
29
  from truefoundry.pydantic_v1 import ValidationError
30
+ from truefoundry.workflow.workflow import (
31
+ TRUEFOUNDRY_LAUNCH_PLAN_NAME,
32
+ execution_config_store,
33
+ )
25
34
 
26
35
 
27
36
  def _handle_code_upload_for_workflow(
@@ -36,14 +45,60 @@ def _handle_code_upload_for_workflow(
36
45
  return new_workflow
37
46
 
38
47
 
48
+ def _is_tfy_workflow_package(package: requirements.parser.Requirement) -> bool:
49
+ return package.name == "truefoundry" and "workflow" in package.extras
50
+
51
+
52
+ def _is_tfy_wf_present_in_pip_packages_or_requirements_file(
53
+ pip_packages: List[str],
54
+ project_root_path: str,
55
+ requirements_path: Optional[str] = None,
56
+ ) -> bool:
57
+ for package in pip_packages:
58
+ parsed_package = requirements.parser.Requirement.parse(package)
59
+ if _is_tfy_workflow_package(parsed_package):
60
+ return True
61
+ if requirements_path:
62
+ requirements_file_absolute_path = os.path.join(
63
+ project_root_path, requirements_path
64
+ )
65
+ if not os.path.exists(requirements_file_absolute_path):
66
+ raise FileNotFoundError(
67
+ f"requirements file not found at {requirements_file_absolute_path}. requirements file path should be relative to project root path."
68
+ )
69
+ with open(requirements_file_absolute_path, "r") as file:
70
+ for package in requirements.parse(file):
71
+ if _is_tfy_workflow_package(package):
72
+ return True
73
+ return False
74
+
75
+
76
+ def _is_tfy_wf_present_in_task_python_build(
77
+ task_image_spec: Dict, project_root_path: str
78
+ ) -> bool:
79
+ pip_packages = task_image_spec["pip_packages"] or []
80
+ requirements_path = task_image_spec.get("requirements_path")
81
+ return _is_tfy_wf_present_in_pip_packages_or_requirements_file(
82
+ pip_packages=pip_packages,
83
+ project_root_path=project_root_path,
84
+ requirements_path=requirements_path,
85
+ )
86
+
87
+
88
+ def _is_dynamic_task(flyte_task: FlyteTaskSpec) -> bool:
89
+ envs: Dict[str:str] = flyte_task.template.container.env or {}
90
+ return SERIALIZED_CONTEXT_ENV_VAR in envs.keys()
91
+
92
+
39
93
  # this function does validation that num_workflows = 1, this also validates task_config is passed correctly.
40
94
  # This is verified by pydantic but doing it here also as error messages are not clear in pydantic
41
- def _validate_workflow_entities(
95
+ def _validate_workflow_entities( # noqa: C901
42
96
  workflow_entities: List[Union[FlyteWorkflowSpec, FlyteLaunchPlan, FlyteTaskSpec]],
97
+ project_root_path: str,
43
98
  ):
44
- workflow_objs = []
45
- launch_plans = []
46
- tasks = []
99
+ workflow_objs: List[FlyteWorkflowSpec] = []
100
+ launch_plans: List[FlyteLaunchPlan] = []
101
+ tasks: List[FlyteTaskSpec] = []
47
102
  for entity in workflow_entities:
48
103
  if isinstance(entity, FlyteWorkflowSpec):
49
104
  workflow_objs.append(entity)
@@ -57,9 +112,9 @@ def _validate_workflow_entities(
57
112
  raise ValueError(
58
113
  f"Workflow file must have exactly one workflow object. Found {len(workflow_objs)}"
59
114
  )
60
- if len(launch_plans) != 1:
115
+ if len(launch_plans) > 2:
61
116
  raise ValueError(
62
- f"Workflow file must have exactly one launch plan. Found {len(launch_plans)}"
117
+ f"Workflow file must have exactly one launch plan. Found {len(launch_plans) - 1}"
63
118
  )
64
119
 
65
120
  error_message_to_use_truefoundry_decorators = """Invalid task definition for task: {}, Please use valid truefoundry decorator/class and pass task_config for tasks.
@@ -69,13 +124,25 @@ def _validate_workflow_entities(
69
124
  `PythonTaskConfig`, or `ContainerTaskConfig`. You can import these using:
70
125
  `from truefoundry.workflow import PythonTaskConfig, ContainerTaskConfig`
71
126
  """
127
+ tasks_without_truefoundry_worflow_package = []
72
128
  for task in tasks:
129
+ if _is_dynamic_task(task):
130
+ raise ValueError("Dynamic workflows are not supported yet.")
73
131
  if not task.template.custom:
74
132
  raise ValueError(
75
133
  error_message_to_use_truefoundry_decorators.format(
76
134
  task.template.id.name
77
135
  )
78
136
  )
137
+ task_image_spec = task.template.custom["truefoundry"]["image"]
138
+ if task_image_spec["type"] == "task-python-build":
139
+ is_tfy_wf_present_in_task_python_build = (
140
+ _is_tfy_wf_present_in_task_python_build(
141
+ task_image_spec=task_image_spec, project_root_path=project_root_path
142
+ )
143
+ )
144
+ if not is_tfy_wf_present_in_task_python_build:
145
+ tasks_without_truefoundry_worflow_package.append(task.template.id.name)
79
146
  try:
80
147
  auto_gen_models.FlyteTaskCustom.validate(task.template.custom)
81
148
  except ValidationError:
@@ -84,6 +151,23 @@ def _validate_workflow_entities(
84
151
  task.template.id.name
85
152
  )
86
153
  ) from None
154
+ if len(tasks_without_truefoundry_worflow_package) > 0:
155
+ raise ValueError(
156
+ rf"truefoundry\[workflow] package is required dependency to run workflows, add it in pip_packages for tasks: {', '.join(tasks_without_truefoundry_worflow_package)}"
157
+ )
158
+
159
+ # validate that all inputs have default values for cron workflows
160
+ for launch_plan in launch_plans:
161
+ if (
162
+ execution_config_store.get(launch_plan.spec.workflow_id.name)
163
+ and launch_plan.id.name == TRUEFOUNDRY_LAUNCH_PLAN_NAME
164
+ ):
165
+ workflow_inputs = launch_plan.spec.default_inputs.parameters
166
+ for input in workflow_inputs:
167
+ if workflow_inputs[input].required:
168
+ raise ValueError(
169
+ f"All inputs must have default for a cron workflow. Input {input} is required in workflow but default value is not provided"
170
+ )
87
171
 
88
172
 
89
173
  def _get_relative_package_path_from_filepath(
@@ -126,10 +210,27 @@ def _generate_manifest_for_workflow(
126
210
  workflow_entities = serialize_workflow(
127
211
  pkgs=[package_path], settings=settings, local_source_root=source_absolute_path
128
212
  )
129
- _validate_workflow_entities(workflow_entities)
213
+ _validate_workflow_entities(workflow_entities, source_absolute_path)
130
214
 
131
215
  workflow.flyte_entities = []
132
216
  for entity in workflow_entities:
217
+ if isinstance(entity, FlyteLaunchPlan):
218
+ workflow_name = entity.spec.workflow_id.name
219
+
220
+ # this is the case when someone has a cron schedule. and this line is for handling default launch plan in this case.
221
+ if (
222
+ execution_config_store.get(workflow_name)
223
+ and workflow_name == entity.id.name
224
+ ):
225
+ continue
226
+ # this is the case when someone does not have a cron schedule. and this line is for handling default launch plan in this case.
227
+ elif entity.id.name == workflow_name:
228
+ entity._id._name = TRUEFOUNDRY_LAUNCH_PLAN_NAME
229
+ # this the case when some workflow doesn't have cron schedule, neither it is default launch plan
230
+ elif entity.id.name != TRUEFOUNDRY_LAUNCH_PLAN_NAME:
231
+ raise ValueError(
232
+ f"Creating launch plans is not allowed. Found launch plan with name {entity.id.name}"
233
+ )
133
234
  message_dict = MessageToDict(entity.to_flyte_idl())
134
235
  # proto message to dict conversion converts all int to float. so we need this hack
135
236
  if (
@@ -148,10 +249,29 @@ def _generate_manifest_for_workflow(
148
249
  auto_gen_models.Workflow.validate({**workflow.dict()})
149
250
 
150
251
 
252
+ def _validate_workspace_fqn(
253
+ workflow: auto_gen_models.Workflow,
254
+ workspace_fqn: Optional[str] = None,
255
+ ):
256
+ if not workspace_fqn:
257
+ raise ValueError(
258
+ "No Workspace FQN was provided. "
259
+ "Pass it explicitly using `--workspace-fqn` argument on CLI or `workspace_fqn` argument of `deploy_workflow`."
260
+ )
261
+
262
+
151
263
  def deploy_workflow(
152
- workflow: auto_gen_models.Workflow, workspace_fqn: str, wait: bool = True
264
+ workflow: auto_gen_models.Workflow,
265
+ workspace_fqn: str,
266
+ wait: bool = True,
153
267
  ) -> Deployment:
154
268
  _generate_manifest_for_workflow(workflow)
269
+ _validate_workspace_fqn(workflow, workspace_fqn)
270
+
271
+ # we need to rest the execution config store as it is a global variable and we don't want to keep the cron execution config for next workflow
272
+ # this is only needed for notebook environment
273
+ execution_config_store.reset()
274
+
155
275
  workspace_id = get_workspace_by_fqn(workspace_fqn).id
156
276
 
157
277
  logger.info(
@@ -67,7 +67,9 @@ class Application(models.Application, DeployablePatchedModelBase):
67
67
  from truefoundry.deploy.v2.lib.deploy_workflow import deploy_workflow
68
68
 
69
69
  return deploy_workflow(
70
- workflow=self.__root__, workspace_fqn=workspace_fqn, wait=wait
70
+ workflow=self.__root__,
71
+ workspace_fqn=workspace_fqn,
72
+ wait=wait,
71
73
  )
72
74
  else:
73
75
  return deploy_component(
@@ -330,18 +330,6 @@ class Endpoint(models.Endpoint, PatchedModelBase):
330
330
  pass
331
331
 
332
332
 
333
- class TruefoundryImageBase(models.TruefoundryImageBase, PatchedModelBase):
334
- type: Literal["truefoundrybase"] = "truefoundrybase"
335
-
336
-
337
- class TruefoundryImageFull(models.TruefoundryImageFull, PatchedModelBase):
338
- type: Literal["truefoundryfull"] = "truefoundryfull"
339
-
340
-
341
- class CodeserverImage(models.CodeserverImage, PatchedModelBase):
342
- type: Literal["codeserver"] = "codeserver"
343
-
344
-
345
333
  class HelmRepo(models.HelmRepo, PatchedModelBase):
346
334
  type: Literal["helm-repo"] = "helm-repo"
347
335
 
@@ -442,10 +430,6 @@ class ArtifactsDownload(models.ArtifactsDownload, PatchedModelBase):
442
430
  pass
443
431
 
444
432
 
445
- class CustomNotebookImage(models.CustomNotebookImage, PatchedModelBase):
446
- type: Literal["customnotebook"] = "customnotebook"
447
-
448
-
449
433
  class NvidiaGPU(models.NvidiaGPU, PatchedModelBase):
450
434
  type: Literal["nvidia_gpu"] = "nvidia_gpu"
451
435
  name: Optional[Union[GPUType, constr(regex=r"^tpu-[a-z\d\-]+$")]] = None
@@ -469,26 +453,6 @@ class GcpTPU(models.GcpTPU, PatchedModelBase):
469
453
  name: Union[TPUType, Literal[r"tpu-[a-z\d\-]+"]]
470
454
 
471
455
 
472
- class CustomCodeserverImage(models.CustomCodeserverImage, PatchedModelBase):
473
- type: Literal["customcodeserver"] = "customcodeserver"
474
-
475
-
476
- class CustomSSHServerImage(models.CustomSSHServerImage, PatchedModelBase):
477
- type: Literal["custom-ssh-server"] = "custom-ssh-server"
478
-
479
-
480
- class SSHServerImage(models.SSHServerImage, PatchedModelBase):
481
- type: Literal["ssh-server"] = "ssh-server"
482
-
483
-
484
- class TruefoundryImageCuda1180(models.TruefoundryImageCuda1180, PatchedModelBase):
485
- type: Literal["truefoundrycuda1180"] = "truefoundrycuda1180"
486
-
487
-
488
- class TruefoundryImageCuda1211(models.TruefoundryImageCuda1211, PatchedModelBase):
489
- type: Literal["truefoundrycuda1211"] = "truefoundrycuda1211"
490
-
491
-
492
456
  class DynamicVolumeConfig(models.DynamicVolumeConfig, PatchedModelBase):
493
457
  type: Literal["dynamic"] = "dynamic"
494
458
 
truefoundry/version.py CHANGED
@@ -1,4 +1,6 @@
1
1
  try:
2
- __version__ = "0.6.2"
2
+ import importlib.metadata
3
+
4
+ __version__ = importlib.metadata.version("truefoundry")
3
5
  except Exception:
4
6
  __version__ = "NA"
@@ -16,4 +16,4 @@ from truefoundry.workflow.container_task import ContainerTask
16
16
  from truefoundry.workflow.map_task import map_task
17
17
  from truefoundry.workflow.python_task import PythonFunctionTask
18
18
  from truefoundry.workflow.task import task
19
- from truefoundry.workflow.workflow import workflow
19
+ from truefoundry.workflow.workflow import ExecutionConfig, workflow
@@ -5,8 +5,9 @@ from truefoundry.workflow import PythonTaskConfig, TaskPythonBuild, task, workfl
5
5
  task_config=PythonTaskConfig(
6
6
  image=TaskPythonBuild(
7
7
  python_version="3.9",
8
- pip_packages=["flytekit==1.10.3"],
9
- )
8
+ pip_packages=["truefoundry[workflow]==0.3.0rc7"],
9
+ ),
10
+ service_account="tfy-flyte-dataplane-devtest-s3",
10
11
  )
11
12
  )
12
13
  def say_hello() -> str:
@@ -1,12 +1,13 @@
1
1
  import os
2
2
  from functools import partial
3
3
  from pathlib import Path
4
- from typing import List, Tuple
4
+ from typing import List, Optional, Tuple
5
5
 
6
6
  from truefoundry.deploy import Image, NvidiaGPU, Resources
7
7
  from truefoundry.workflow import (
8
8
  ContainerTask,
9
9
  ContainerTaskConfig,
10
+ ExecutionConfig,
10
11
  FlyteDirectory,
11
12
  PythonTaskConfig,
12
13
  TaskPythonBuild,
@@ -19,15 +20,17 @@ from truefoundry.workflow import (
19
20
  cpu_task_config = PythonTaskConfig(
20
21
  image=TaskPythonBuild(
21
22
  python_version="3.9",
22
- pip_packages=["flytekit==1.10.3"],
23
+ pip_packages=["truefoundry[workflow]==0.3.0rc7"],
23
24
  ),
24
25
  resources=Resources(cpu_request=0.45),
26
+ service_account="tfy-flyte-dataplane-devtest-s3",
25
27
  )
26
28
 
27
29
 
28
30
  # figure out naming
29
31
  @task(task_config=cpu_task_config)
30
32
  def should_train_tokenizer(tokenizer: str) -> bool:
33
+ print("Should train tokenizer")
31
34
  return not bool(tokenizer)
32
35
 
33
36
 
@@ -38,6 +41,7 @@ def should_train_tokenizer(tokenizer: str) -> bool:
38
41
  # cache_version="1.0",
39
42
  )
40
43
  def train_tokenizer() -> str:
44
+ print("Training tokenizer")
41
45
  return "trained_tokenizer"
42
46
 
43
47
 
@@ -45,7 +49,7 @@ def train_tokenizer() -> str:
45
49
  task_config=PythonTaskConfig(
46
50
  image=TaskPythonBuild(
47
51
  python_version="3.9",
48
- pip_packages=["flytekit==1.10.3", "pynvml==11.5.0"],
52
+ pip_packages=["truefoundry[workflow]==0.3.0rc7", "pynvml==11.5.0"],
49
53
  cuda_version="11.5-cudnn8",
50
54
  ),
51
55
  env={
@@ -53,17 +57,17 @@ def train_tokenizer() -> str:
53
57
  "NVIDIA_VISIBLE_DEVICES": "all",
54
58
  },
55
59
  resources=Resources(cpu_request=0.45, devices=[NvidiaGPU(name="T4", count=1)]),
60
+ service_account="tfy-flyte-dataplane-devtest-s3",
56
61
  ),
57
62
  )
58
63
  def train_model(tokenizer: str) -> Tuple[FlyteDirectory, str]:
64
+ print("Training model")
59
65
  import flytekit
60
- from package.test_workflow import random
61
66
  from pynvml import nvmlDeviceGetCount, nvmlInit
62
67
 
63
68
  nvmlInit()
64
69
  assert nvmlDeviceGetCount() > 0
65
70
 
66
- random.random()
67
71
  working_dir = flytekit.current_context().working_directory
68
72
  local_dir = Path(os.path.join(working_dir, "csv_files"))
69
73
  local_dir.mkdir(exist_ok=True)
@@ -76,6 +80,7 @@ def train_model(tokenizer: str) -> Tuple[FlyteDirectory, str]:
76
80
 
77
81
  @task(task_config=cpu_task_config)
78
82
  def get_validation_data() -> List[str]:
83
+ print("Getting validation data")
79
84
  return ["foo", "bar", "baz"]
80
85
 
81
86
 
@@ -89,6 +94,7 @@ def validate_model(model: FlyteDirectory, tokenizer: str, validation_data: str)
89
94
 
90
95
  @task(task_config=cpu_task_config)
91
96
  def all_good(validations: List[bool]) -> bool:
97
+ print("Validations", validations)
92
98
  return all(validations)
93
99
 
94
100
 
@@ -97,18 +103,33 @@ echo = ContainerTask(
97
103
  task_config=ContainerTaskConfig(
98
104
  image=Image(
99
105
  image_uri="bash:4.1",
100
- )
106
+ command=["echo", "hello"],
107
+ ),
108
+ service_account="tfy-flyte-dataplane-devtest-s3",
101
109
  ),
102
110
  )
103
111
 
104
112
 
105
113
  @task(task_config=cpu_task_config)
106
114
  def random(tokenizer: str) -> Tuple[FlyteDirectory, str]:
115
+ print(tokenizer)
107
116
  return FlyteDirectory(path=""), "random"
108
117
 
109
118
 
110
- @workflow
111
- def train(tokenizer: str = "") -> bool:
119
+ @workflow(
120
+ execution_configs=[
121
+ ExecutionConfig(
122
+ schedule="*/10 * * * *",
123
+ )
124
+ ]
125
+ )
126
+ def train(
127
+ tokenizer: str = "",
128
+ test_v2: str = "",
129
+ optional_var: Optional[str] = "",
130
+ default_v: Optional[str] = "hello1",
131
+ default_v2: str = "hello2",
132
+ ) -> bool:
112
133
  stt = should_train_tokenizer(tokenizer=tokenizer)
113
134
  model, t = (
114
135
  conditional("train_tokenizer")
@@ -1,5 +1,5 @@
1
1
  type: workflow
2
- name: my-test-workflow5
2
+ name: my-test-workflow9
3
3
  source:
4
4
  type: local
5
5
  local_build: false
@@ -1,6 +1,8 @@
1
- from typing import Any, Callable, Optional, Union
1
+ from typing import Any, Callable, Dict, List, Optional, Union
2
2
 
3
3
  from flytekit.core.base_task import Task
4
+ from flytekit.core.launch_plan import LaunchPlan
5
+ from flytekit.core.schedule import CronSchedule
4
6
  from flytekit.core.workflow import (
5
7
  FuncOut,
6
8
  PythonFunctionWorkflow,
@@ -8,11 +10,39 @@ from flytekit.core.workflow import (
8
10
  )
9
11
  from flytekit.core.workflow import workflow as flytekit_workflow
10
12
 
13
+ from truefoundry.pydantic_v1 import BaseModel
14
+
15
+ TRUEFOUNDRY_LAUNCH_PLAN_NAME = "default"
16
+
17
+
18
+ class ExecutionConfig(BaseModel):
19
+ # I am not using job's Schedule here because:
20
+ # 1. flyte accepts schedule only as a string
21
+ # 2. flyte doesn't support setting concurrency policy
22
+ schedule: str
23
+
24
+
25
+ class ExecutionConfigStore(BaseModel):
26
+ execution_config_map: Dict[str, ExecutionConfig] = {}
27
+
28
+ def reset(self):
29
+ self.execution_config_map = {}
30
+
31
+ def get(self, key: str) -> Optional[ExecutionConfig]:
32
+ return self.execution_config_map.get(key)
33
+
34
+ def set(self, key: str, value: ExecutionConfig):
35
+ self.execution_config_map[key] = value
36
+
37
+
38
+ execution_config_store = ExecutionConfigStore()
39
+
11
40
 
12
41
  def workflow(
13
42
  _workflow_function: Optional[Callable[..., Any]] = None,
14
43
  failure_policy: Optional[WorkflowFailurePolicy] = None,
15
44
  on_failure: Optional[Task] = None,
45
+ execution_configs: Optional[List[ExecutionConfig]] = None,
16
46
  ) -> Union[
17
47
  Callable[[Callable[..., FuncOut]], PythonFunctionWorkflow],
18
48
  PythonFunctionWorkflow,
@@ -47,8 +77,38 @@ def workflow(
47
77
  :param on_failure: Invoke this workflow or task on failure. The Workflow / task has to match the signature of
48
78
  the current workflow, with an additional parameter called `error` Error
49
79
  """
50
- return flytekit_workflow(
80
+ if _workflow_function is None:
81
+
82
+ def wrapper(func: Callable[..., Any]) -> Any:
83
+ return workflow(
84
+ _workflow_function=func,
85
+ failure_policy=failure_policy,
86
+ on_failure=on_failure,
87
+ execution_configs=execution_configs,
88
+ )
89
+
90
+ return wrapper
91
+
92
+ workflow_object = flytekit_workflow(
51
93
  _workflow_function,
52
94
  failure_policy=failure_policy,
53
95
  on_failure=on_failure,
54
96
  )
97
+ execution_configs = execution_configs or []
98
+ if len(execution_configs) > 1:
99
+ raise ValueError("Only one cron execution config is allowed per workflow")
100
+
101
+ if execution_configs:
102
+ execution_config = execution_configs[0]
103
+ # flyte maintains this in-memory and uses it while serialization
104
+ LaunchPlan.get_or_create(
105
+ workflow=workflow_object,
106
+ name=TRUEFOUNDRY_LAUNCH_PLAN_NAME,
107
+ schedule=CronSchedule(schedule=execution_config.schedule),
108
+ )
109
+ function_module = _workflow_function.__module__
110
+ function_name = _workflow_function.__name__
111
+ function_path = f"{function_module}.{function_name}"
112
+ execution_config_store.set(function_path, execution_config)
113
+
114
+ return workflow_object
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: truefoundry
3
- Version: 0.3.0rc7
3
+ Version: 0.3.0rc9
4
4
  Summary: Truefoundry CLI
5
5
  Author: Abhishek Choudhary
6
6
  Author-email: abhishek@truefoundry.com
@@ -34,6 +34,7 @@ Requires-Dist: python-dotenv (>=1.0.1,<1.1.0)
34
34
  Requires-Dist: python-socketio[client] (>=5.5.2,<6.0.0)
35
35
  Requires-Dist: questionary (>=1.10.0,<2.0.0)
36
36
  Requires-Dist: requests (>=2.31.0,<3.0.0)
37
+ Requires-Dist: requirements-parser (>=0.10.2,<0.11.0)
37
38
  Requires-Dist: rich (>=13.7.1,<14.0.0)
38
39
  Requires-Dist: rich-click (>=1.2.1,<2.0.0)
39
40
  Requires-Dist: tqdm (>=4.0.0,<5.0.0)
@@ -25,13 +25,13 @@ truefoundry/autodeploy/utils/diff.py,sha256=Ef8Y-VffDKel_-q-GxRam6gqiv8qTLMcqVg6
25
25
  truefoundry/autodeploy/utils/pydantic_compat.py,sha256=hEAUy5kLjhPdzw7yGZ2iXGMXbbMVXVlGzIofmyHafXQ,412
26
26
  truefoundry/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  truefoundry/cli/__main__.py,sha256=Jap_IddZ9zNyMIyIkCw75xHQCN0WtV2dPZJ_pzdLsVc,916
28
- truefoundry/deploy/__init__.py,sha256=0iIsvCvEwDlnzuPIFIFesCD4tmOEn8vUmnn6R2ck2ns,2470
29
- truefoundry/deploy/auto_gen/models.py,sha256=mYaO9_vknbPJtq9_kyp1IOhX3ppi6jCRpx3-wCz8dU4,86586
30
- truefoundry/deploy/builder/__init__.py,sha256=GSDz4smq-t750FpHaKGiauGoZilYmdZmaITXVWvGD6I,5032
28
+ truefoundry/deploy/__init__.py,sha256=hyBoxhL3SG37vduJBdUYJuy1EHGeSLPI4S8MuKhyCFo,2259
29
+ truefoundry/deploy/auto_gen/models.py,sha256=inof7aH6Wk1z15YYtCNJo7sp3Tixv5yaJzB_4fiZux4,78412
30
+ truefoundry/deploy/builder/__init__.py,sha256=a1qR6nicHGcxRaeNTxWRsmDs8zsmXc7j13-I8q0qqVk,4938
31
31
  truefoundry/deploy/builder/builders/__init__.py,sha256=tlFLXqyDaKLd4iZbo4Hcu_8gOmgtL6drnXpbmQ6x1P8,636
32
32
  truefoundry/deploy/builder/builders/dockerfile.py,sha256=AXXTziCkaqIhuM_bwyD1vT1znOwemN1TKgU7eyo-KuM,1522
33
- truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py,sha256=eKbwgGV8ONe22iKW_0f3X0uQlw1t6z7kdKV4MPVkLfA,1341
34
- truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py,sha256=V63vlJbrfErgGXNWxpwYir8S4GAmcNXvFoqPDaKXdNQ,1387
33
+ truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py,sha256=OK2lCwaPfhh7ALjNqfFBOTv7B9B3e9WDLoaSm0LWEkk,1436
34
+ truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py,sha256=cEjpcz2L9lEEoEcF0rzuRpgXwNkuce8dbvdBvhTqDYk,1946
35
35
  truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py,sha256=n7MwUzIyn3mFqVkQqOuMeswmSPsydpYH4XMV5c8qAdQ,1381
36
36
  truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py,sha256=vFmFeK38-t8booJEGREapEjrIL8xnyOQeRSJQq7d3ZQ,6183
37
37
  truefoundry/deploy/builder/docker_service.py,sha256=vQS15790njzlFJZ3JW6txYLBdT11ltxqqpf78ZFL_Ng,5208
@@ -43,13 +43,13 @@ truefoundry/deploy/cli/commands/build_command.py,sha256=QwKkZ3nVecPMzs-R57YP-_ih
43
43
  truefoundry/deploy/cli/commands/build_logs_command.py,sha256=WrPOlFec_wwuzdJmKZ8mjca-oFVvxgfblcqj2LlhWJA,2804
44
44
  truefoundry/deploy/cli/commands/create_command.py,sha256=ZjA4EP1jHYuVE1zx0kN-giBr3y0sEiXnu8xMsNyD2Rg,1850
45
45
  truefoundry/deploy/cli/commands/delete_command.py,sha256=4tyIameA1pVu9uZZNJPK6rqdV-cJogf51iCCrG-9noI,2390
46
- truefoundry/deploy/cli/commands/deploy_command.py,sha256=qag9B-7r6KlVkAn4GtODjzUi_degXcXcF-GdNctMMPk,2642
46
+ truefoundry/deploy/cli/commands/deploy_command.py,sha256=SpCd_1OxA8ZEUyJ6Jkf5Ay8UNakCCLGR--svMHp9EEM,2858
47
47
  truefoundry/deploy/cli/commands/get_command.py,sha256=w7h5C4bJpmHJ99rgiGg9J_X0xi8aZUeB6Q-SoZUV1tg,5979
48
48
  truefoundry/deploy/cli/commands/list_command.py,sha256=cFARY22L5xspBX7TWsx41IF4RiRMok7KBwv7hQRFXNs,4498
49
49
  truefoundry/deploy/cli/commands/login_command.py,sha256=Kin6ZdmeADvXOGA08M5_t1MwMnu3QEUfjz1seJurstw,1032
50
50
  truefoundry/deploy/cli/commands/logout_command.py,sha256=hgw6LhJtF3xJ6YyQ_VrBuUWY0WmT8tPYbOEOhMu1eXU,550
51
51
  truefoundry/deploy/cli/commands/logs_command.py,sha256=fQSY4o_B78WEJDD5qULWPFQJsCXLJLTr0RI1QjmX1WE,4154
52
- truefoundry/deploy/cli/commands/patch_application_command.py,sha256=ymAns8QDuAY08kYxkX93DlcDSLApF126KXiXrWJtSqY,2366
52
+ truefoundry/deploy/cli/commands/patch_application_command.py,sha256=Zuswj36o-qniklOsOjEPUKnd9f5mHv_U7rOPPwALrZ8,2457
53
53
  truefoundry/deploy/cli/commands/patch_command.py,sha256=OQCmxcn1Qd2iZfBUaqs48oUKjHVZ13SoMsWLhksPvks,1682
54
54
  truefoundry/deploy/cli/commands/redeploy_command.py,sha256=-wMQLeMcBdJsQBMgTv7rS6hCqH1EJsSKgPrAXU9HjxU,1032
55
55
  truefoundry/deploy/cli/commands/terminate_comand.py,sha256=RTNuykw5DBvid44u2nCka_Eu0XSK3Qrzwob9fybHLgQ,1097
@@ -102,11 +102,11 @@ truefoundry/deploy/lib/util.py,sha256=3Pc0gQgomapTSjMQo743STShBn1B6PNdBpw3bTpdxO
102
102
  truefoundry/deploy/lib/win32.py,sha256=1RcvPTdlOAJ48rt8rCbE2Ufha2ztRqBAE9dueNXArrY,5009
103
103
  truefoundry/deploy/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
104
  truefoundry/deploy/v2/lib/__init__.py,sha256=WEiVMZXOVljzEE3tpGJil14liIn_PCDoACJ6b3tZ6sI,188
105
- truefoundry/deploy/v2/lib/deploy.py,sha256=mR8wJ86jJFbko87TZYhIsEylQK5cj2X6BNq_uf-Img4,10123
106
- truefoundry/deploy/v2/lib/deploy_workflow.py,sha256=0URc5UJ6x9F1Kxt8fLetugm76s77NpZpuyK1NIzFv-o,6879
107
- truefoundry/deploy/v2/lib/deployable_patched_models.py,sha256=dp1Av532iBat6fHetwgN4G2ijii3thyUP2fyyOmQMIM,3108
105
+ truefoundry/deploy/v2/lib/deploy.py,sha256=vmaevT86nfrgephRjinhSVFEZHVc9R09vZBKKUoeUWI,11640
106
+ truefoundry/deploy/v2/lib/deploy_workflow.py,sha256=o_8gd98SFgijvrYDubm6cJ7XNqlXRPwswq8nRXfqvDM,12171
107
+ truefoundry/deploy/v2/lib/deployable_patched_models.py,sha256=YVeTiasyf0s-QXjDqdvGSmN83oVAQ0h9g6F687PJyVI,3141
108
108
  truefoundry/deploy/v2/lib/models.py,sha256=pSolLMTArDuYpeNsmeeS5DWliloN_iCDfZSpRllMHUg,1120
109
- truefoundry/deploy/v2/lib/patched_models.py,sha256=4gQg1mvRZCVqN00tiLbGHoq_GdZKTgzxzXXgZCvFQfk,15020
109
+ truefoundry/deploy/v2/lib/patched_models.py,sha256=qL3N7y6zf7jywx6uY-yMxsH65NB367V_82o47ZYJt1I,13820
110
110
  truefoundry/deploy/v2/lib/source.py,sha256=VHKuFREiixUP40D3Mrz-TA70spu1M0RbCzl--qwlFaY,9263
111
111
  truefoundry/langchain/__init__.py,sha256=zeYKxKrQhfYXQuBec3wvB_ZqKowDUUjLUKUhbiu9ZFs,558
112
112
  truefoundry/langchain/deprecated.py,sha256=8tfLHXwcifGl7DYhMNfzc4zRVCVqEgARg5BsbZp11NE,10835
@@ -118,19 +118,19 @@ truefoundry/logger.py,sha256=7dLqW_Q2rEgo-_z1WZnQbGHaoy1L1MP3NqGgssaSS6o,685
118
118
  truefoundry/ml/__init__.py,sha256=yFjvF-e1RW488vLHgn5M7TXoajqww6grkKHb3mhqDEw,179
119
119
  truefoundry/pydantic_v1.py,sha256=IJy5FtaPg-_H5XLRd9nueTuyijEJPdCa1vZ7-pinmRI,158
120
120
  truefoundry/python_deploy_codegen.py,sha256=hd8XRytuT-U6AXSzYSjISbtYY4HLB_B1EeLB5TRDfNw,3947
121
- truefoundry/version.py,sha256=mefOHzC5MR9WiuhSLX7Jrw94GOeB6PVRhqbyWKM1HSA,72
122
- truefoundry/workflow/__init__.py,sha256=Dna_uQA93GM5BvhY6Y7KLmqWvoXqU1LwefyrqRSDES8,642
121
+ truefoundry/version.py,sha256=bqiT4Q-VWrTC6P4qfK43mez-Ppf-smWfrl6DcwV7mrw,137
122
+ truefoundry/workflow/__init__.py,sha256=m0puY8RJ3GBvdAXW0A9PLuCpmDdLqh6eF0t3DCEpZkg,659
123
123
  truefoundry/workflow/container_task.py,sha256=8arieePsX4__OnG337hOtCiNgJwtKJJCsZcmFmCBJtk,402
124
124
  truefoundry/workflow/example/deploy.sh,sha256=wfbPRrCi04WYRqCf4g-Xo12uWbcqPD6G_Tz0lV0jU_U,60
125
- truefoundry/workflow/example/hello_world_package/workflow.py,sha256=yksM7Nl8OOVb8qNL2gY7j-a8_F519hRw31trenTjhfE,385
126
- truefoundry/workflow/example/package/test_workflow.py,sha256=HG9F3bTZSLfTn-U5zOqIVFnmTEzlACxuWy4RbN9sV1o,3371
127
- truefoundry/workflow/example/truefoundry.yaml,sha256=OVmlc71vSIraDY9VuD3yJzu-WpKGWejv_JBoUAr-ONI,226
125
+ truefoundry/workflow/example/hello_world_package/workflow.py,sha256=ACWJJWTY5NhYyU_dAEfdSqXgyLuNXPRDpOBuUkTEzhU,459
126
+ truefoundry/workflow/example/package/test_workflow.py,sha256=-CeCcMyI3fjRGSkzfmmnLcE2aFxceCUpEXx8dql8MBE,4015
127
+ truefoundry/workflow/example/truefoundry.yaml,sha256=LlPrMADSPJsiXRoK76N_RVjX1bnZ3FH1u2jXrwLfR9I,226
128
128
  truefoundry/workflow/example/workflow.yaml,sha256=YtYdKXMuW_08gfEo21XSculj2MGI2lfEnGF8qCT8NKE,2858
129
129
  truefoundry/workflow/map_task.py,sha256=2m3qGXQ90k9LdS45q8dqCCECc3qr8t2m_LMCVd1mZ7g,1737
130
130
  truefoundry/workflow/python_task.py,sha256=SRXRLC4vdBqGjhkwuaY39LEWN6iPCpJAuW17URRdWTY,1128
131
131
  truefoundry/workflow/task.py,sha256=ToitYiKcNzFCtOVQwz1W8sRjbR97eVS7vQBdbgUQtKg,1779
132
- truefoundry/workflow/workflow.py,sha256=w0bcWjBByThN_Yi7SaB_T1bilwbQizk-sr492Nfdwcs,2555
133
- truefoundry-0.3.0rc7.dist-info/METADATA,sha256=y2DyuWzhCWG76MKNMM0yJ1rgdjeOj08LIoInA8WdhCo,2639
134
- truefoundry-0.3.0rc7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
135
- truefoundry-0.3.0rc7.dist-info/entry_points.txt,sha256=TXvUxQkI6zmqJuycPsyxEIMr3oqfDjgrWj0m_9X12x4,95
136
- truefoundry-0.3.0rc7.dist-info/RECORD,,
132
+ truefoundry/workflow/workflow.py,sha256=WaTqUjhwfAXDWu4E5ehuwAxrCbDJkoAf1oWmR2E9Qy0,4575
133
+ truefoundry-0.3.0rc9.dist-info/METADATA,sha256=MoFxdl5KpAr9mCShojPeE2O2dXsgyB82DSjYKPIR9b0,2693
134
+ truefoundry-0.3.0rc9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
135
+ truefoundry-0.3.0rc9.dist-info/entry_points.txt,sha256=TXvUxQkI6zmqJuycPsyxEIMr3oqfDjgrWj0m_9X12x4,95
136
+ truefoundry-0.3.0rc9.dist-info/RECORD,,