truefoundry 0.12.0rc1__py3-none-any.whl → 0.12.1__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.

@@ -36,11 +36,16 @@ class TrueFoundrySdkEnv(BaseSettings):
36
36
  # Note: Every field in this class should have a default value
37
37
  # Never expect the user to set these values
38
38
 
39
+ # For local development, this enables further configuration via _TFYServersConfig
40
+ TFY_CLI_LOCAL_DEV_MODE: bool = False
41
+
39
42
  # These two are not meant to be used directly. See the TFY_HOST and TFY_API_KEY properties below
40
43
  TFY_HOST_: Optional[str] = Field(default=None, env=TFY_HOST_ENV_KEY)
41
44
  TFY_API_KEY_: Optional[SecretStr] = Field(default=None, env=TFY_API_KEY_ENV_KEY)
42
45
 
43
- # Internal Signed URL Server
46
+ ##############################
47
+ # Internal Signed URL Server #
48
+ ##############################
44
49
  TFY_INTERNAL_SIGNED_URL_SERVER_HOST: Optional[str] = Field(
45
50
  default=None, env=TFY_INTERNAL_SIGNED_URL_SERVER_HOST_ENV_KEY
46
51
  )
@@ -55,32 +60,42 @@ class TrueFoundrySdkEnv(BaseSettings):
55
60
  TFY_INTERNAL_SIGNED_URL_REQUEST_TIMEOUT: int = 3600 # default: 1 hour
56
61
  TFY_INTERNAL_SIGNED_URL_CLIENT_LOG_LEVEL: str = "WARNING"
57
62
 
58
- # Artifacts
63
+ #############
64
+ # Artifacts #
65
+ #############
59
66
  TFY_ARTIFACTS_DOWNLOAD_CHUNK_SIZE_BYTES: int = 100 * 1000 * 1000
60
67
  TFY_ARTIFACTS_DOWNLOAD_MAX_WORKERS: int = max(min(32, (os.cpu_count() or 2) * 2), 4)
61
68
  TFY_ARTIFACTS_UPLOAD_MAX_WORKERS: int = max(min(32, (os.cpu_count() or 2) * 2), 4)
62
69
  TFY_ARTIFACTS_DISABLE_MULTIPART_UPLOAD: bool = False
63
70
  TFY_ARTIFACTS_DOWNLOAD_FSYNC_CHUNKS: bool = False
64
71
 
65
- # Fo customizing the python image used for building via PythonBuild
72
+ #############
73
+ # TFY Build #
74
+ #############
75
+ # For customizing the images and packages used for builds
66
76
  TFY_PYTHONBUILD_PYTHON_IMAGE_REPO: str = "public.ecr.aws/docker/library/python"
67
-
68
- # TODO(gw): Use another image with more linient rate limits
69
- TFY_SPARK_BUILD_SPARK_IMAGE_REPO: str = "public.ecr.aws/bitnami/spark"
70
-
71
- TFY_TASK_PYSPARK_BUILD_SPARK_IMAGE_REPO: str = "public.ecr.aws/bitnami/spark"
72
-
73
- # For local development, this enables futher configuration via _TFYServersConfig
74
- TFY_CLI_LOCAL_DEV_MODE: bool = False
75
-
76
77
  TFY_PYTHON_BUILD_PACKAGE_MANAGER: PythonPackageManager = PythonPackageManager.UV
77
78
  TFY_PYTHON_BUILD_UV_IMAGE_REPO: str = "ghcr.io/astral-sh/uv"
78
79
  TFY_PYTHON_BUILD_UV_IMAGE_TAG: str = "latest"
80
+ TFY_PYTHON_BUILD_POETRY_VERSION: str = Field(
81
+ default="2.0", env="TFY_PYTHON_BUILD_POETRY_VERSION"
82
+ )
83
+ TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION: int = Field(
84
+ default=2, env="TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION"
85
+ )
86
+ TFY_SPARK_BUILD_SPARK_IMAGE_REPO: str = "public.ecr.aws/bitnami/spark"
87
+ TFY_TASK_PYSPARK_BUILD_SPARK_IMAGE_REPO: str = "public.ecr.aws/bitnami/spark"
79
88
 
89
+ ##############
90
+ # OpenAI API #
91
+ ##############
80
92
  # Global Constants for OpenAI Integration
81
93
  OPENAI_API_KEY: Optional[str] = Field(default=None, env=OPENAI_API_KEY_KEY)
82
94
  OPENAI_MODEL: Optional[str] = Field(default=None, env=OPENAI_MODEL_KEY)
83
95
 
96
+ ###########
97
+ # TFY Ask #
98
+ ###########
84
99
  # Ask Command Specific Environment Variables for TrueFoundry
85
100
  TFY_ASK_OPENAI_API_KEY: Optional[str] = Field(
86
101
  default=None, env=TFY_ASK_OPENAI_API_KEY_KEY
@@ -94,12 +109,6 @@ class TrueFoundrySdkEnv(BaseSettings):
94
109
  )
95
110
  TFY_ASK_SYSTEM_PROMPT_NAME: str = Field(default="tfy-ask-k8s-prompt")
96
111
  TFY_INTERNAL_ASK_CONFIG_OVERRIDE_FILE: Optional[str] = Field(default=None)
97
- TFY_PYTHON_BUILD_POETRY_VERSION: str = Field(
98
- default="2.0", env="TFY_PYTHON_BUILD_POETRY_VERSION"
99
- )
100
- TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION: int = Field(
101
- default=2, env="TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION"
102
- )
103
112
 
104
113
  # This is a hack to fresh read the env vars because people can end up importing this file
105
114
  # before setting the correct env vars. E.g. in notebook environments.
@@ -210,3 +210,7 @@ def get_user_agent() -> str:
210
210
  return f"truefoundry/{__version__} Python/{platform.python_version()} OS/{platform.system()}-{platform.release()} ({platform.architecture()[0]})"
211
211
  except Exception:
212
212
  return f"truefoundry/{__version__}"
213
+
214
+
215
+ def get_expanded_and_absolute_path(path: str):
216
+ return os.path.abspath(os.path.expanduser(path))
@@ -14,22 +14,20 @@ def _get_expanded_and_absolute_path(path: str):
14
14
 
15
15
  def _build_docker_image(
16
16
  tag: str,
17
+ dockerfile: str,
17
18
  path: str = ".",
18
- file: Optional[str] = None,
19
19
  build_args: Optional[Dict[str, str]] = None,
20
20
  extra_opts: Optional[List[str]] = None,
21
21
  ):
22
+ dockerfile = _get_expanded_and_absolute_path(dockerfile)
22
23
  path = _get_expanded_and_absolute_path(path)
23
24
 
24
- if file:
25
- file = _get_expanded_and_absolute_path(file)
26
-
27
25
  build_docker_image(
28
26
  path=path,
29
27
  tag=tag,
30
28
  # TODO: can we pick target platform(s) picked from cluster
31
29
  platform="linux/amd64",
32
- dockerfile=file,
30
+ dockerfile=dockerfile,
33
31
  build_args=build_args,
34
32
  extra_opts=extra_opts,
35
33
  )
@@ -50,8 +48,8 @@ def build(
50
48
 
51
49
  _build_docker_image(
52
50
  tag=tag,
51
+ dockerfile=build_configuration.dockerfile_path,
53
52
  path=build_configuration.build_context_path,
54
- file=build_configuration.dockerfile_path,
55
53
  build_args=build_configuration.build_args,
56
54
  extra_opts=extra_opts,
57
55
  )
@@ -6,6 +6,7 @@ from jinja2 import Template
6
6
  from truefoundry.common.constants import ENV_VARS, PythonPackageManager
7
7
  from truefoundry.deploy._autogen.models import UV, Pip, Poetry
8
8
  from truefoundry.deploy.builder.utils import (
9
+ check_whether_poetry_toml_exists,
9
10
  generate_apt_install_command,
10
11
  generate_command_to_install_from_uv_lock,
11
12
  generate_pip_install_command,
@@ -18,7 +19,6 @@ from truefoundry.deploy.v2.lib.patched_models import (
18
19
  CUDAVersion,
19
20
  PythonBuild,
20
21
  _resolve_requirements_path,
21
- check_whether_poetry_toml_exists,
22
22
  )
23
23
  from truefoundry.pydantic_v1 import BaseModel
24
24
 
@@ -224,6 +224,10 @@ def _build_template_context(
224
224
  package_manager: str,
225
225
  available_secrets: Set[str],
226
226
  ) -> TemplateContext:
227
+ if not build_configuration.python_version:
228
+ raise ValueError(
229
+ "`python_version` is required for `tfy-python-buildpack` builder"
230
+ )
227
231
  # Set up package manager config secret mount
228
232
  python_dependencies_type = python_dependencies.type
229
233
  package_manager_config_secret_mount = ""
@@ -235,7 +239,7 @@ def _build_template_context(
235
239
  )
236
240
 
237
241
  # Configure dependencies based on type
238
- if python_dependencies_type == "uv":
242
+ if isinstance(python_dependencies, UV):
239
243
  uv_context = _build_uv_context(
240
244
  python_dependencies,
241
245
  available_secrets,
@@ -252,7 +256,7 @@ def _build_template_context(
252
256
  ],
253
257
  final_uv_sync_command=uv_context["final_uv_sync_command"],
254
258
  )
255
- elif python_dependencies_type == "poetry":
259
+ elif isinstance(python_dependencies, Poetry):
256
260
  poetry_toml_exists = check_whether_poetry_toml_exists(
257
261
  build_context_path=build_configuration.build_context_path,
258
262
  )
@@ -271,7 +275,7 @@ def _build_template_context(
271
275
  poetry_version_expression=poetry_context["poetry_version_expression"],
272
276
  poetry_toml_exists=poetry_toml_exists,
273
277
  )
274
- elif python_dependencies_type == "pip":
278
+ elif isinstance(python_dependencies, Pip):
275
279
  pip_context = _build_pip_context(
276
280
  build_configuration,
277
281
  python_dependencies,
@@ -10,9 +10,6 @@ from truefoundry.deploy.builder.utils import (
10
10
  generate_uv_pip_install_command,
11
11
  get_available_secrets,
12
12
  )
13
- from truefoundry.deploy.v2.lib.patched_models import (
14
- _resolve_requirements_path,
15
- )
16
13
 
17
14
  # TODO (chiragjn): Switch to a non-root user inside the container
18
15
 
@@ -79,10 +76,7 @@ def generate_dockerfile_content(
79
76
  available_secrets = get_available_secrets(docker_build_extra_args)
80
77
 
81
78
  # TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
82
- requirements_path = _resolve_requirements_path(
83
- build_context_path=build_configuration.build_context_path,
84
- requirements_path=build_configuration.requirements_path,
85
- )
79
+ requirements_path = build_configuration.requirements_path
86
80
  requirements_destination_path = (
87
81
  "/tmp/requirements.txt" if requirements_path else None
88
82
  )
@@ -11,9 +11,6 @@ from truefoundry.deploy.builder.utils import (
11
11
  generate_uv_pip_install_command,
12
12
  get_available_secrets,
13
13
  )
14
- from truefoundry.deploy.v2.lib.patched_models import (
15
- _resolve_requirements_path,
16
- )
17
14
 
18
15
  # TODO[GW]: Switch to a non-root user inside the container
19
16
  _POST_PYTHON_INSTALL_TEMPLATE = """
@@ -65,17 +62,10 @@ def generate_dockerfile_content(
65
62
  available_secrets = get_available_secrets(docker_build_extra_args)
66
63
 
67
64
  # TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
68
- requirements_path = _resolve_requirements_path(
69
- build_context_path="",
70
- requirements_path=build_configuration.requirements_path,
71
- )
65
+ requirements_path = build_configuration.requirements_path
72
66
  requirements_destination_path = (
73
67
  "/tmp/requirements.txt" if requirements_path else None
74
68
  )
75
- # if not build_configuration.python_version:
76
- # raise ValueError(
77
- # "`python_version` is required for `tfy-python-buildpack` builder"
78
- # )
79
69
  pip_packages = get_additional_pip_packages(build_configuration) + (
80
70
  build_configuration.pip_packages or []
81
71
  )
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import shlex
2
3
  from typing import List, Optional, Set
3
4
 
@@ -307,3 +308,15 @@ def generate_poetry_install_command(
307
308
  )
308
309
 
309
310
  return poetry_install_cmd
311
+
312
+
313
+ def check_whether_poetry_toml_exists(
314
+ build_context_path: str,
315
+ ) -> bool:
316
+ required_filename = "poetry.toml"
317
+ possible_path = os.path.join(build_context_path, required_filename)
318
+
319
+ if os.path.isfile(possible_path):
320
+ return True
321
+
322
+ return False
@@ -2,7 +2,13 @@ import os
2
2
  import re
3
3
  from typing import Union
4
4
 
5
+ from truefoundry.common.utils import get_expanded_and_absolute_path
5
6
  from truefoundry.deploy._autogen.models import (
7
+ UV,
8
+ Build,
9
+ DockerFileBuild,
10
+ Pip,
11
+ Poetry,
6
12
  PythonBuild,
7
13
  )
8
14
 
@@ -36,31 +42,110 @@ def find_list_paths(data, parent_key="", sep="."):
36
42
  return list_paths
37
43
 
38
44
 
39
- def _validate_file_path(project_root_path: str, file_path: str):
40
- file_absolute_path = os.path.join(project_root_path, file_path)
41
- if not os.path.exists(file_absolute_path):
42
- raise FileNotFoundError(
43
- f"file {file_path} not found. file path should be relative to your project root path: {project_root_path}."
45
+ def _validate_file_path(
46
+ parent_path: str, relative_file_path: str, parent_dir_type: str
47
+ ):
48
+ parent_abs = get_expanded_and_absolute_path(parent_path)
49
+ file_path_abs = get_expanded_and_absolute_path(
50
+ os.path.join(parent_abs, relative_file_path)
51
+ )
52
+ # Ensure the file path is actually inside the build context
53
+ outside_context = False
54
+ try:
55
+ # Use os.path.commonpath to check if file_path is inside build_context_abs
56
+ common_path = os.path.commonpath([parent_abs, file_path_abs])
57
+ outside_context = common_path != parent_abs
58
+ except ValueError:
59
+ # os.path.commonpath raises ValueError if paths are on different drives (Windows)
60
+ outside_context = True
61
+
62
+ if outside_context:
63
+ raise ValueError(
64
+ f"Referenced file `{relative_file_path}` is outside the {parent_dir_type} `{parent_abs}`. "
65
+ f"It must exist in {parent_dir_type} `{parent_abs}`."
44
66
  )
45
67
 
68
+ if not os.path.exists(file_path_abs):
69
+ raise ValueError(
70
+ f"Referenced file `{relative_file_path}` not found. It must exist in {parent_dir_type} `{parent_abs}`."
71
+ )
72
+
73
+
74
+ def validate_dockerfile_build_paths(
75
+ dockerfile_build: DockerFileBuild, project_root_path: str
76
+ ):
77
+ _validate_file_path(
78
+ parent_path=project_root_path,
79
+ relative_file_path=dockerfile_build.dockerfile_path,
80
+ parent_dir_type="project root",
81
+ )
82
+
46
83
 
47
- def validate_paths(python_build: PythonBuild, source_dir: str):
84
+ def validate_python_build_paths(python_build: PythonBuild, build_context_path: str):
48
85
  if not python_build.python_dependencies:
86
+ # Old style flat requirements file
87
+ if python_build.requirements_path:
88
+ _validate_file_path(
89
+ parent_path=build_context_path,
90
+ relative_file_path=python_build.requirements_path,
91
+ parent_dir_type="build context",
92
+ )
49
93
  return
50
94
 
51
- if not os.path.exists(source_dir):
52
- raise ValueError(f"project root path {source_dir!r} of does not exist")
53
95
  if (
54
- python_build.python_dependencies.type == "pip"
96
+ isinstance(python_build.python_dependencies, Pip)
55
97
  and python_build.python_dependencies.requirements_path
56
98
  ):
57
99
  _validate_file_path(
58
- source_dir,
59
- python_build.python_dependencies.requirements_path,
100
+ parent_path=build_context_path,
101
+ relative_file_path=python_build.python_dependencies.requirements_path,
102
+ parent_dir_type="build context",
103
+ )
104
+ elif isinstance(python_build.python_dependencies, UV):
105
+ _validate_file_path(
106
+ parent_path=build_context_path,
107
+ relative_file_path="pyproject.toml",
108
+ parent_dir_type="build context",
109
+ )
110
+ _validate_file_path(
111
+ parent_path=build_context_path,
112
+ relative_file_path="uv.lock",
113
+ parent_dir_type="build context",
114
+ )
115
+ elif isinstance(python_build.python_dependencies, Poetry):
116
+ _validate_file_path(
117
+ parent_path=build_context_path,
118
+ relative_file_path="pyproject.toml",
119
+ parent_dir_type="build context",
120
+ )
121
+ _validate_file_path(
122
+ parent_path=build_context_path,
123
+ relative_file_path="poetry.lock",
124
+ parent_dir_type="build context",
125
+ )
126
+
127
+
128
+ def validate_local_source_paths(component_name: str, build: Build):
129
+ source_dir = get_expanded_and_absolute_path(build.build_source.project_root_path)
130
+ if not os.path.exists(source_dir):
131
+ raise ValueError(
132
+ f"Project root path {source_dir!r} of component {component_name!r} does not exist"
133
+ )
134
+
135
+ build_context_path = get_expanded_and_absolute_path(
136
+ os.path.join(source_dir, build.build_spec.build_context_path)
137
+ )
138
+ if not os.path.exists(build_context_path):
139
+ raise ValueError(
140
+ f"Build context path {build_context_path!r} "
141
+ f"of component {component_name!r} does not exist"
142
+ )
143
+
144
+ if isinstance(build.build_spec, DockerFileBuild):
145
+ validate_dockerfile_build_paths(
146
+ dockerfile_build=build.build_spec, project_root_path=source_dir
147
+ )
148
+ elif isinstance(build.build_spec, PythonBuild):
149
+ validate_python_build_paths(
150
+ python_build=build.build_spec, build_context_path=build_context_path
60
151
  )
61
- if python_build.python_dependencies.type == "uv":
62
- _validate_file_path(source_dir, "uv.lock")
63
- _validate_file_path(source_dir, "pyproject.toml")
64
- if python_build.python_dependencies.type == "poetry":
65
- _validate_file_path(source_dir, "pyproject.toml")
66
- _validate_file_path(source_dir, "poetry.lock")
@@ -15,6 +15,7 @@ from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
15
15
  from truefoundry.deploy.lib.model.entity import Deployment, DeploymentTransitionStatus
16
16
  from truefoundry.deploy.lib.util import (
17
17
  get_application_fqn_from_deployment_fqn,
18
+ validate_local_source_paths,
18
19
  )
19
20
  from truefoundry.deploy.v2.lib.models import BuildResponse
20
21
  from truefoundry.deploy.v2.lib.source import (
@@ -47,6 +48,9 @@ def _handle_if_local_source(component: Component, workspace_fqn: str) -> Compone
47
48
  and isinstance(component.image.build_source, autogen_models.LocalSource)
48
49
  ):
49
50
  new_component = component.copy(deep=True)
51
+ validate_local_source_paths(
52
+ component_name=new_component.name, build=new_component.image
53
+ )
50
54
 
51
55
  if new_component.image.build_source.local_build:
52
56
  if not env_has_docker():
@@ -47,8 +47,9 @@ or set it to None if you don't want to use any requirements file.
47
47
  """
48
48
 
49
49
  SPECS_UPGRADE_WARNING_MESSAGE_TEMPLATE = """\
50
- The `requirements_path` and `pip_packages` fields are deprecated and will be removed in a future release.
51
- Please use the `python_dependencies` field instead. Please use the following format:
50
+ The `requirements_path` and `pip_packages` fields are deprecated.
51
+ It is recommended to use the `python_dependencies` field instead which supports pip, uv and poetry.
52
+ Please use the following format:
52
53
 
53
54
  ```python
54
55
  build=Build(
@@ -106,18 +107,6 @@ def _resolve_requirements_path(
106
107
  return None
107
108
 
108
109
 
109
- def check_whether_poetry_toml_exists(
110
- build_context_path: str,
111
- ) -> bool:
112
- required_filename = "poetry.toml"
113
- possible_path = os.path.join(build_context_path, required_filename)
114
-
115
- if os.path.isfile(possible_path):
116
- return True
117
-
118
- return False
119
-
120
-
121
110
  class CUDAVersion(str, enum.Enum):
122
111
  CUDA_11_0_CUDNN8 = "11.0-cudnn8"
123
112
  CUDA_11_1_CUDNN8 = "11.1-cudnn8"
@@ -210,7 +199,9 @@ class PythonBuild(models.PythonBuild, PatchedModelBase):
210
199
  requirements_path=values.get("requirements_path"),
211
200
  )
212
201
 
213
- if not values.get("python_dependencies"):
202
+ if (
203
+ values.get("requirements_path") or values.get("pip_packages")
204
+ ) and not values.get("python_dependencies"):
214
205
  warnings.warn(
215
206
  SPECS_UPGRADE_WARNING_MESSAGE_TEMPLATE.format(
216
207
  requirements_txt_path=values.get("requirements_path"),
@@ -219,27 +210,12 @@ class PythonBuild(models.PythonBuild, PatchedModelBase):
219
210
  category=TrueFoundryDeprecationWarning,
220
211
  stacklevel=2,
221
212
  )
222
- values["python_dependencies"] = Pip(
223
- type="pip",
224
- requirements_path=values.get("requirements_path"),
225
- pip_packages=values.get("pip_packages"),
226
- )
227
- values.pop("pip_packages", None)
228
- values.pop("requirements_path", None)
229
213
  return values
230
214
 
231
215
 
232
216
  class SparkBuild(models.SparkBuild, PatchedModelBase):
233
217
  type: Literal["tfy-spark-buildpack"] = "tfy-spark-buildpack"
234
218
 
235
- @root_validator
236
- def validate_values(cls, values):
237
- _resolve_requirements_path(
238
- build_context_path=values.get("build_context_path") or "./",
239
- requirements_path=values.get("requirements_path"),
240
- )
241
- return values
242
-
243
219
 
244
220
  class SparkImageBuild(models.SparkImageBuild, PatchedModelBase):
245
221
  type: Literal["spark-image-build"] = "spark-image-build"
@@ -9,6 +9,7 @@ import gitignorefile
9
9
  from tqdm import tqdm
10
10
 
11
11
  from truefoundry.common.types import UploadCodePackageCallable
12
+ from truefoundry.common.utils import get_expanded_and_absolute_path
12
13
  from truefoundry.common.warnings import TrueFoundryDeprecationWarning
13
14
  from truefoundry.deploy import builder
14
15
  from truefoundry.deploy._autogen import models
@@ -21,7 +22,6 @@ from truefoundry.deploy.lib.clients.servicefoundry_client import (
21
22
  ServiceFoundryServiceClient,
22
23
  )
23
24
  from truefoundry.deploy.lib.dao import workspace as workspace_lib
24
- from truefoundry.deploy.lib.util import validate_paths
25
25
  from truefoundry.deploy.v2.lib.patched_models import Image, RemoteSource
26
26
  from truefoundry.logger import logger
27
27
 
@@ -142,15 +142,8 @@ def local_source_to_remote_source(
142
142
  ) -> RemoteSource:
143
143
  with tempfile.TemporaryDirectory() as local_dir:
144
144
  package_local_path = os.path.join(local_dir, "build.tar.gz")
145
- source_dir = os.path.abspath(local_source.project_root_path)
146
-
147
- if not os.path.exists(source_dir):
148
- raise ValueError(
149
- f"project root path {source_dir!r} of component {component_name!r} does not exist"
150
- )
151
-
145
+ source_dir = get_expanded_and_absolute_path(local_source.project_root_path)
152
146
  logger.info("Archiving contents of dir: %r", source_dir)
153
-
154
147
  is_path_ignored = _get_callback_handler_to_ignore_file_path(source_dir)
155
148
  _make_tarfile(
156
149
  output_filename=package_local_path,
@@ -183,36 +176,14 @@ def local_source_to_image(
183
176
  component_name: str,
184
177
  ) -> Image:
185
178
  build = build.copy(deep=True)
186
- source_dir = os.path.abspath(
187
- os.path.expanduser(build.build_source.project_root_path)
188
- )
189
- if not os.path.exists(source_dir):
190
- raise ValueError(
191
- f"project root path {source_dir!r} of component {component_name!r} does not exist"
192
- )
193
-
194
- build.build_spec.build_context_path = os.path.join(
195
- source_dir, build.build_spec.build_context_path
196
- )
197
- if not os.path.exists(build.build_spec.build_context_path):
198
- raise ValueError(
199
- f"Build context path {build.build_spec.build_context_path!r} "
200
- f"of component {component_name!r} does not exist"
201
- )
202
-
179
+ source_dir = get_expanded_and_absolute_path(build.build_source.project_root_path)
203
180
  if isinstance(build.build_spec, models.DockerFileBuild):
204
181
  build.build_spec.dockerfile_path = os.path.join(
205
182
  source_dir, build.build_spec.dockerfile_path
206
183
  )
207
- if not os.path.exists(build.build_spec.dockerfile_path):
208
- raise ValueError(
209
- f"Dockerfile path {build.build_spec.dockerfile_path!r} "
210
- f"of component {component_name!r} does not exist"
211
- )
212
-
213
- if isinstance(build.build_spec, models.PythonBuild):
214
- validate_paths(build.build_spec, source_dir)
215
-
184
+ build.build_spec.build_context_path = os.path.join(
185
+ source_dir, build.build_spec.build_context_path
186
+ )
216
187
  client = ServiceFoundryServiceClient()
217
188
 
218
189
  workspace = workspace_lib.get_workspace_by_fqn(workspace_fqn=workspace_fqn)
@@ -40,6 +40,7 @@ from truefoundry.ml.log_types.artifacts.utils import (
40
40
  calculate_total_size,
41
41
  get_autogen_type,
42
42
  set_tfy_internal_metadata,
43
+ set_user_artifact_metadata,
43
44
  )
44
45
  from truefoundry.ml.logger import logger
45
46
  from truefoundry.ml.session import _get_api_client
@@ -458,6 +459,7 @@ def _log_artifact_version_helper(
458
459
 
459
460
  metadata = metadata or {}
460
461
  metadata = set_tfy_internal_metadata(metadata)
462
+ metadata = set_user_artifact_metadata(metadata)
461
463
 
462
464
  _validate_description(description)
463
465
  _validate_artifact_metadata(metadata)
@@ -26,7 +26,7 @@ following:
26
26
  from truefoundry.ml import get_client
27
27
  from truefoundry.ml import ModelVersion, ModelSchema, Feature, FeatureValueType, PredictionValueType
28
28
  client = get_client()
29
- model_version = ModelVersion(fqn="{fqn}")
29
+ model_version = ModelVersion.from_fqn(fqn="{fqn}")
30
30
  model_version = ModelSchema(...) # or schema in dictionary format {{"features": [...], "prediction": ...}}
31
31
  model_version.update()
32
32
  ```
@@ -36,3 +36,6 @@ ARTIFACT_METADATA_TRUEFOUNDRY_KEY = ".truefoundry"
36
36
  TFY_INTERNAL_APPLICATION_ID_ENV_VAR = "TFY_INTERNAL_APPLICATION_ID"
37
37
  TFY_INTERNAL_APPLICATION_VERSION_ENV_VAR = "TFY_INTERNAL_APPLICATION_VERSION"
38
38
  TFY_INTERNAL_JOB_RUN_NAME_ENV_VAR = "TFY_INTERNAL_JOB_RUN_NAME"
39
+ TFY_ARTIFACTS_ADDITIONAL_USER_METADATA_ENV_VAR = (
40
+ "TFY_ARTIFACTS_ADDITIONAL_USER_METADATA"
41
+ )
@@ -47,6 +47,7 @@ from truefoundry.ml.log_types.artifacts.utils import (
47
47
  calculate_total_size,
48
48
  get_autogen_type,
49
49
  set_tfy_internal_metadata,
50
+ set_user_artifact_metadata,
50
51
  )
51
52
  from truefoundry.ml.model_framework import (
52
53
  ModelFrameworkType,
@@ -547,6 +548,7 @@ def _log_model_version( # noqa: C901
547
548
  total_size = None
548
549
  metadata = metadata or {}
549
550
  metadata = set_tfy_internal_metadata(metadata)
551
+ metadata = set_user_artifact_metadata(metadata)
550
552
 
551
553
  _validate_description(description)
552
554
  _validate_artifact_metadata(metadata)
@@ -10,6 +10,7 @@ from truefoundry.ml.exceptions import MlFoundryException
10
10
  from truefoundry.ml.log_types.artifacts.constants import (
11
11
  ARTIFACT_METADATA_TRUEFOUNDRY_KEY,
12
12
  DESCRIPTION_MAX_LENGTH,
13
+ TFY_ARTIFACTS_ADDITIONAL_USER_METADATA_ENV_VAR,
13
14
  TFY_INTERNAL_APPLICATION_ID_ENV_VAR,
14
15
  TFY_INTERNAL_APPLICATION_VERSION_ENV_VAR,
15
16
  TFY_INTERNAL_JOB_RUN_NAME_ENV_VAR,
@@ -237,16 +238,8 @@ def get_autogen_type(parent_type: Type[BaseModel], field_name: str) -> Type[Base
237
238
 
238
239
 
239
240
  def set_tfy_internal_metadata(metadata: Dict[str, Any]) -> Dict[str, Any]:
240
- if ARTIFACT_METADATA_TRUEFOUNDRY_KEY not in metadata:
241
- metadata[ARTIFACT_METADATA_TRUEFOUNDRY_KEY] = {}
242
- elif not isinstance(metadata[ARTIFACT_METADATA_TRUEFOUNDRY_KEY], dict):
243
- return metadata
244
-
245
- tfy_metadata = metadata[ARTIFACT_METADATA_TRUEFOUNDRY_KEY]
246
- created_by_key = "created_by"
247
- if created_by_key not in tfy_metadata:
248
- tfy_metadata[created_by_key] = {}
249
- elif not isinstance(tfy_metadata[created_by_key], dict):
241
+ tfy_metadata = metadata.setdefault(ARTIFACT_METADATA_TRUEFOUNDRY_KEY, {})
242
+ if not isinstance(tfy_metadata, dict):
250
243
  return metadata
251
244
 
252
245
  tfy_internal_metadata = {
@@ -254,8 +247,53 @@ def set_tfy_internal_metadata(metadata: Dict[str, Any]) -> Dict[str, Any]:
254
247
  "application_version": os.environ.get(TFY_INTERNAL_APPLICATION_VERSION_ENV_VAR),
255
248
  "job_run_name": os.environ.get(TFY_INTERNAL_JOB_RUN_NAME_ENV_VAR),
256
249
  }
257
- created_by_metadata = tfy_metadata[created_by_key]
258
- for key, value in tfy_internal_metadata.items():
259
- if key not in created_by_metadata and value is not None:
260
- created_by_metadata[key] = value
250
+ tfy_internal_metadata = {
251
+ k: v for k, v in tfy_internal_metadata.items() if v is not None
252
+ }
253
+
254
+ if tfy_internal_metadata:
255
+ created_by_key = "created_by"
256
+ created_by_metadata = tfy_metadata.setdefault(created_by_key, {})
257
+ if created_by_key in tfy_metadata and not isinstance(created_by_metadata, dict):
258
+ return metadata
259
+ for key, value in tfy_internal_metadata.items():
260
+ if key not in created_by_metadata:
261
+ created_by_metadata[key] = value
262
+ return metadata
263
+
264
+
265
+ def _merge_dicts_recursively(
266
+ src: Dict[str, Any], dest: Dict[str, Any], overwrite: bool = False
267
+ ) -> Dict[str, Any]:
268
+ for key, value in src.items():
269
+ if key not in dest:
270
+ dest[key] = value
271
+ else:
272
+ if isinstance(value, dict) and isinstance(dest[key], dict):
273
+ _merge_dicts_recursively(value, dest[key], overwrite=overwrite)
274
+ elif overwrite:
275
+ dest[key] = value
276
+ return dest
277
+
278
+
279
+ def set_user_artifact_metadata(metadata: Dict[str, Any]) -> Dict[str, Any]:
280
+ _user_metadata = os.environ.get(TFY_ARTIFACTS_ADDITIONAL_USER_METADATA_ENV_VAR)
281
+ if not _user_metadata:
282
+ return metadata
283
+
284
+ try:
285
+ user_metadata = json.loads(_user_metadata)
286
+ except ValueError:
287
+ logger.warning(
288
+ "Content of `TFY_ARTIFACTS_ADDITIONAL_USER_METADATA` environment variable is not valid json, cannot add to metadata"
289
+ )
290
+ return metadata
291
+
292
+ if not isinstance(user_metadata, dict):
293
+ logger.warning(
294
+ "Content of `TFY_ARTIFACTS_ADDITIONAL_USER_METADATA` environment variable is not a dictionary, cannot add to metadata"
295
+ )
296
+ return metadata
297
+
298
+ metadata = _merge_dicts_recursively(user_metadata, metadata, overwrite=False)
261
299
  return metadata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truefoundry
3
- Version: 0.12.0rc1
3
+ Version: 0.12.1
4
4
  Summary: TrueFoundry CLI
5
5
  Author-email: TrueFoundry Team <abhishek@truefoundry.com>
6
6
  Requires-Python: <3.14,>=3.8.1
@@ -40,7 +40,7 @@ truefoundry/cli/display_util.py,sha256=9vzN3mbQqU6OhS7qRUiMRana4PTHa4sDTA0Hn7OVj
40
40
  truefoundry/cli/util.py,sha256=kEjC20-n_jwxZV9jq-78CxDk4xAySxAoYIXTxZfJzLM,5423
41
41
  truefoundry/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  truefoundry/common/auth_service_client.py,sha256=N3YxKlx63r6cPZqbgb2lqBOPI69ShB7D7RCIq4FSCjc,7949
43
- truefoundry/common/constants.py,sha256=UdCa5nuBpNldleMsRGKK9F_5jEN93uLFeC3laGKVLzc,5010
43
+ truefoundry/common/constants.py,sha256=-oZRizJixgt7nKAtGoFyHN23sIbe1UqLkYVAyEYacbM,5203
44
44
  truefoundry/common/credential_file_manager.py,sha256=1yEk1Zm2xS4G0VDFwKSZ4w0VUrcPWQ1nJnoBaz9xyKA,4251
45
45
  truefoundry/common/credential_provider.py,sha256=_OhJ2XFlDaVsrUO-FyywxctcGGqDdC2pgcvwEKqQD0Q,4071
46
46
  truefoundry/common/entities.py,sha256=b4R6ss06-ygDS3C4Tqa_GOq5LFKDYbt7x4Mghnfz6yo,4007
@@ -50,7 +50,7 @@ truefoundry/common/servicefoundry_client.py,sha256=2fYhdVPSvLXz5C5tosOq86JD8WM3I
50
50
  truefoundry/common/session.py,sha256=d9l3TEBpqVP4mr4mTGY1qVxc815skzMlNNdw14otg34,2923
51
51
  truefoundry/common/storage_provider_utils.py,sha256=jX9maCtWusZx63324cmPxHiGeIluZZzbDaJ1QYTIhAw,11856
52
52
  truefoundry/common/types.py,sha256=BMJFCsR1lPJAw66IQBSvLyV4I6o_x5oj78gVsUa9si8,188
53
- truefoundry/common/utils.py,sha256=P0FuAadoJGdpieUORLSN-PiFnkyoGO-K2cS4OPITBWg,6714
53
+ truefoundry/common/utils.py,sha256=SMpinsdXuAkIUXe7gBB8v-dWe1x2_VHdXTkA0btksDc,6816
54
54
  truefoundry/common/warnings.py,sha256=xDMhR_-ZGC40Ycaj6nlFb5MYPexn8WbKCHd4FlflTXQ,705
55
55
  truefoundry/deploy/__init__.py,sha256=g_g5_XKCZZXL3v_ycQhvn01bkMHweNqhOy1rg6vc5-k,2903
56
56
  truefoundry/deploy/python_deploy_codegen.py,sha256=WwP6bIzFoLpF7J2Bgef2HMSIeefJ8TWtSv4hXNycEzQ,8872
@@ -58,18 +58,18 @@ truefoundry/deploy/_autogen/models.py,sha256=FwhfLrqrjFWY8Mzny2hlL2ksqDfVUMFFJsP
58
58
  truefoundry/deploy/builder/__init__.py,sha256=7MEezQzxjrWpbvnONyGMqYNVVonlb8WkARob4dHzAB4,5045
59
59
  truefoundry/deploy/builder/constants.py,sha256=aWC94kL8I8Lty9ccJwBVncsNx4ADTgPxyBxk_zur6XM,980
60
60
  truefoundry/deploy/builder/docker_service.py,sha256=sm7GWeIqyrKaZpxskdLejZlsxcZnM3BTDJr6orvPN4E,3948
61
- truefoundry/deploy/builder/utils.py,sha256=idgXqjhzyXqevV3NZlFCSkATASCLfMf1LLT8z19l8kM,10188
61
+ truefoundry/deploy/builder/utils.py,sha256=YJVwSfzwc5OF6rDd_Gf-R1J0llARTvQ2CsauQrGP7_I,10465
62
62
  truefoundry/deploy/builder/builders/__init__.py,sha256=Gp9NODR1E7mUjadhzIe3zzO43bBfHPeNcEDryYF2uo0,807
63
- truefoundry/deploy/builder/builders/dockerfile.py,sha256=XMbMlPUTMPCyaHl7jJQY1ODtlRkpI61PcvgG6Ck5jNc,1522
63
+ truefoundry/deploy/builder/builders/dockerfile.py,sha256=PoEyWBm5NmBDIL0pAzPPt2IWXbAQTVOZ-psT5ZXxQdI,1517
64
64
  truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py,sha256=RGWGqY8xOF7vycUPJd10N7ZzahWv24lO0anrOPtLuDU,1796
65
65
  truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py,sha256=rQgdvKmAT9HArVW4TAG5yd2QTKRs3S5LJ9RQbc_EkHE,2518
66
66
  truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py,sha256=CGBZvmLoYLgFI98-1ulXJU-tt90OcDFJaV4dFw-aThE,1763
67
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py,sha256=96ltZcRZPe1A9QedWH9yEiBdVnBwhPyc7OCOV6589lY,16278
67
+ truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py,sha256=MGLxzrOeOr6WycqssMugmh4e44f870hP7LpRoGOT9aQ,16448
68
68
  truefoundry/deploy/builder/builders/tfy_spark_buildpack/__init__.py,sha256=4d-1lZrR70QN2fTsAaphWNRwXGx5IG99yFs6gEp7TFE,2625
69
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/dockerfile_template.py,sha256=YLLpBBNx8Ww8CSqVrwy4yEVlFhnPoY9OfxDiIXUXD_w,4410
69
+ truefoundry/deploy/builder/builders/tfy_spark_buildpack/dockerfile_template.py,sha256=m9NMv8tDQvYQs8v5wB9hCHdJRM7L2ct3OOcAg5l0JfQ,4193
70
70
  truefoundry/deploy/builder/builders/tfy_spark_buildpack/tfy_execute_notebook.py,sha256=-D37Zjy2SBt3RHxonPEpR1_LR0W7vTSM1kQ1S-fdK-I,6363
71
71
  truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/__init__.py,sha256=9LkPTAydx8UVOfKfEqfe5t7vgnc2GuNiUafCv5jcUVw,1556
72
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/dockerfile_template.py,sha256=xEZsQr5QTYuMPpM2sBViYZP6qnW2uunI8ElpVX3EBRk,4394
72
+ truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/dockerfile_template.py,sha256=3W8_lMIVgQFc8p9btU4wOAduiOUXB-hnnbqAQ7K5Eks,4044
73
73
  truefoundry/deploy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
74
  truefoundry/deploy/cli/commands/__init__.py,sha256=qv818jxqSAygJ3h-6Ul8t-5VOgR_UrSgsVtNCl3e5G0,1408
75
75
  truefoundry/deploy/cli/commands/apply_command.py,sha256=DmXmKVokkauyKIiJDtErTwbJ5_LvQeJbTQsG5BjyKpo,2427
@@ -101,7 +101,7 @@ truefoundry/deploy/lib/diff_utils.py,sha256=J1sAf5O4rHNHS_MOkcB0CyaeQFtU_ALI-wd-
101
101
  truefoundry/deploy/lib/logs_utils.py,sha256=SQxRv3jDDmgHdOUMhlMaAPGYskybnBUMpst7QU_i_sc,1469
102
102
  truefoundry/deploy/lib/messages.py,sha256=8424kj3kqCyDCX5Nr2WJZZ_UEutPoaSs_y2f9-O4yy8,1001
103
103
  truefoundry/deploy/lib/session.py,sha256=fLdgR6ZDp8-hFl5NTON4ngnWLsMzGxvKtfpDOOw_7lo,4963
104
- truefoundry/deploy/lib/util.py,sha256=I-rRwzgPhkLfz0cHN2xEjX48V8SM5SrPhTtXUgjoTj4,2324
104
+ truefoundry/deploy/lib/util.py,sha256=QLED4zF_g_WE67MCrZQy1nrLcuiLwInLEqxYdLmOu90,5326
105
105
  truefoundry/deploy/lib/win32.py,sha256=1RcvPTdlOAJ48rt8rCbE2Ufha2ztRqBAE9dueNXArrY,5009
106
106
  truefoundry/deploy/lib/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
107
  truefoundry/deploy/lib/clients/servicefoundry_client.py,sha256=JIj0Rs5PVZzXeh2QubLaVjgMJiUkfHrIMTtZMpgBmiA,27369
@@ -115,12 +115,12 @@ truefoundry/deploy/lib/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
115
115
  truefoundry/deploy/lib/model/entity.py,sha256=eBfA4trO0jUuDy0wifiu2rB_HryZrx5Kf-tRMwIQ_9g,8716
116
116
  truefoundry/deploy/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
117
  truefoundry/deploy/v2/lib/__init__.py,sha256=WEiVMZXOVljzEE3tpGJil14liIn_PCDoACJ6b3tZ6sI,188
118
- truefoundry/deploy/v2/lib/deploy.py,sha256=M7z5tTPLan_s7ZwmGl_erl9lyz1CirNi8vBqg5yvSCc,12720
118
+ truefoundry/deploy/v2/lib/deploy.py,sha256=JaLK0Mryps_2JaPB-q3NjfA0d55xsMFbIkH8VddRd_E,12873
119
119
  truefoundry/deploy/v2/lib/deploy_workflow.py,sha256=G5BzMIbap8pgDX1eY-TITruUxQdkKhYtBmRwLL6lDeY,14342
120
120
  truefoundry/deploy/v2/lib/deployable_patched_models.py,sha256=mUi-OjPf7bc8rzfrPLdFb79LKuDq7F36RxL4V-AXebs,6830
121
121
  truefoundry/deploy/v2/lib/models.py,sha256=ogc1UYs1Z2nBdGSKCrde9sk8d0GxFKMkem99uqO5CmM,1148
122
- truefoundry/deploy/v2/lib/patched_models.py,sha256=fVlH8umtOGq4qYjVgIHl5uz2SODPy98S2pxpRdawJug,19087
123
- truefoundry/deploy/v2/lib/source.py,sha256=oO3DhdafGtyLeFktxLXbfhYJUuWFRwfGAm7x0jgxUjE,9571
122
+ truefoundry/deploy/v2/lib/patched_models.py,sha256=gWpU7aVLVM-QFqUuekLbTO7ZJvWbaijzAuLgl1FwZC0,18345
123
+ truefoundry/deploy/v2/lib/source.py,sha256=nG11vrPqc2Z2bv7IMRruB-fNbSxY8jeWYNcCTEXr5s0,8622
124
124
  truefoundry/ml/__init__.py,sha256=EEEHV7w58Krpo_W9Chd8Y3TdItfFO3LI6j6Izqc4-P8,2219
125
125
  truefoundry/ml/constants.py,sha256=vDq72d4C9FSWqr9MMdjgTF4TuyNFApvo_6RVsSeAjB4,2837
126
126
  truefoundry/ml/entities.py,sha256=GuwzmS7qqZJ_iz34zhla_Pg-ZNjt_3oHG2gn-LMftKk,1486
@@ -365,12 +365,12 @@ truefoundry/ml/log_types/plot.py,sha256=LDh4uy6z2P_a2oPM2lc85c0lt8utVvunohzeMawF
365
365
  truefoundry/ml/log_types/pydantic_base.py,sha256=eBlw_AEyAz4iJKDP4zgJOCFWcldwQqpf7FADW1jzIQY,272
366
366
  truefoundry/ml/log_types/utils.py,sha256=xjJ21jdPScvFmw3TbVh5NCzbzJwaqiXJyiiT4xxX1EI,335
367
367
  truefoundry/ml/log_types/artifacts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
368
- truefoundry/ml/log_types/artifacts/artifact.py,sha256=OXJDaJZLGE8jA-6A8pdApfpbz1x3O-8rIMWUkG_8hYA,19940
369
- truefoundry/ml/log_types/artifacts/constants.py,sha256=7blG13UXSkZVZkN8EDFqaH-a_ZonJA6pI0gwJwkIZ-s,1298
368
+ truefoundry/ml/log_types/artifacts/artifact.py,sha256=Ra2R5BYuLhvLlxaCIwzoluZ7SrnZTSz-H8H9z_0c23E,20024
369
+ truefoundry/ml/log_types/artifacts/constants.py,sha256=BU4tpFQ7p3rNUkSvQXZNGRJB1QjkrQW2eRspUG9HOTs,1405
370
370
  truefoundry/ml/log_types/artifacts/dataset.py,sha256=OgWIoT59AhMw8P01FfvUKbJ3EL6HQf_Xw8X4E3Ff5Sg,13172
371
371
  truefoundry/ml/log_types/artifacts/general_artifact.py,sha256=yr-SQ2fhUR_sE1MB5zoHHYpGC8tizH_-t3lhsxCAULU,2747
372
- truefoundry/ml/log_types/artifacts/model.py,sha256=j4Gf0TKpi-JCOBqCdkH40pkpDLwvSk04KgQn2sK5fZI,24911
373
- truefoundry/ml/log_types/artifacts/utils.py,sha256=INZhhzl6OaD6qFAyxaLJAhXhFd6wKNujMj-lq2v8p4Q,9340
372
+ truefoundry/ml/log_types/artifacts/model.py,sha256=lGxj2cl0q_dgBGJTTOvIA2mUYio32x3uL-IssPa-UpU,24995
373
+ truefoundry/ml/log_types/artifacts/utils.py,sha256=AXSBMUjAb8hm0Wbnj6a8UZbLs5gIWNIkp1RJReNtnGM,10639
374
374
  truefoundry/ml/log_types/image/__init__.py,sha256=fcOq8yQnNj1rkLcPeIjLXBpdA1WIeiPsXOlAAvMxx7M,76
375
375
  truefoundry/ml/log_types/image/constants.py,sha256=wLtGEOA4T5fZHSlOXPuNDLX3lpbCtwlvGKPFk_1fah0,255
376
376
  truefoundry/ml/log_types/image/image.py,sha256=sa0tBHdyluC8bELXY16E0HgFrUDnDBxHrteix4BFXcs,12479
@@ -387,7 +387,7 @@ truefoundry/workflow/remote_filesystem/__init__.py,sha256=LQ95ViEjJ7Ts4JcCGOxMPs
387
387
  truefoundry/workflow/remote_filesystem/logger.py,sha256=em2l7D6sw7xTLDP0kQSLpgfRRCLpN14Qw85TN7ujQcE,1022
388
388
  truefoundry/workflow/remote_filesystem/tfy_signed_url_client.py,sha256=xcT0wQmQlgzcj0nP3tJopyFSVWT1uv3nhiTIuwfXYeg,12342
389
389
  truefoundry/workflow/remote_filesystem/tfy_signed_url_fs.py,sha256=nSGPZu0Gyd_jz0KsEE-7w_BmnTD8CVF1S8cUJoxaCbc,13305
390
- truefoundry-0.12.0rc1.dist-info/METADATA,sha256=NxF_EilSNyo5V3bU2FYER04ratIXGFMzB1tzM5BC6H8,2799
391
- truefoundry-0.12.0rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
392
- truefoundry-0.12.0rc1.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
393
- truefoundry-0.12.0rc1.dist-info/RECORD,,
390
+ truefoundry-0.12.1.dist-info/METADATA,sha256=vlWYE5rhOSxQxqxoaiJO-obasmN55g_pCwaQDZBZ0O8,2796
391
+ truefoundry-0.12.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
392
+ truefoundry-0.12.1.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
393
+ truefoundry-0.12.1.dist-info/RECORD,,