truefoundry 0.5.0rc7__py3-none-any.whl → 0.5.1rc2__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.

Files changed (52) hide show
  1. truefoundry/common/utils.py +73 -1
  2. truefoundry/deploy/__init__.py +5 -0
  3. truefoundry/deploy/cli/cli.py +2 -0
  4. truefoundry/deploy/cli/commands/__init__.py +1 -0
  5. truefoundry/deploy/cli/commands/deploy_init_command.py +22 -0
  6. truefoundry/deploy/lib/dao/application.py +2 -1
  7. truefoundry/deploy/v2/lib/patched_models.py +8 -0
  8. truefoundry/ml/__init__.py +14 -12
  9. truefoundry/ml/autogen/client/__init__.py +5 -0
  10. truefoundry/ml/autogen/client/api/mlfoundry_artifacts_api.py +161 -0
  11. truefoundry/ml/autogen/client/models/__init__.py +5 -0
  12. truefoundry/ml/autogen/client/models/artifact_version_manifest.py +2 -2
  13. truefoundry/ml/autogen/client/models/export_deployment_files_request_dto.py +82 -0
  14. truefoundry/ml/autogen/client/models/infer_method_name.py +34 -0
  15. truefoundry/ml/autogen/client/models/model_server.py +34 -0
  16. truefoundry/ml/autogen/client/models/model_version_environment.py +1 -1
  17. truefoundry/ml/autogen/client/models/model_version_manifest.py +3 -3
  18. truefoundry/ml/autogen/client/models/sklearn_framework.py +17 -1
  19. truefoundry/ml/autogen/client/models/transformers_framework.py +2 -2
  20. truefoundry/ml/autogen/client/models/xg_boost_framework.py +6 -1
  21. truefoundry/ml/autogen/client_README.md +4 -0
  22. truefoundry/ml/autogen/entities/artifacts.py +29 -7
  23. truefoundry/ml/cli/commands/model_init.py +97 -0
  24. truefoundry/ml/cli/utils.py +34 -0
  25. truefoundry/ml/log_types/artifacts/model.py +63 -24
  26. truefoundry/ml/log_types/artifacts/utils.py +37 -1
  27. truefoundry/ml/mlfoundry_api.py +74 -78
  28. truefoundry/ml/mlfoundry_run.py +0 -30
  29. truefoundry/ml/model_framework.py +257 -3
  30. truefoundry/ml/validation_utils.py +2 -0
  31. {truefoundry-0.5.0rc7.dist-info → truefoundry-0.5.1rc2.dist-info}/METADATA +1 -5
  32. {truefoundry-0.5.0rc7.dist-info → truefoundry-0.5.1rc2.dist-info}/RECORD +34 -46
  33. truefoundry/deploy/function_service/__init__.py +0 -3
  34. truefoundry/deploy/function_service/__main__.py +0 -27
  35. truefoundry/deploy/function_service/app.py +0 -92
  36. truefoundry/deploy/function_service/build.py +0 -45
  37. truefoundry/deploy/function_service/remote/__init__.py +0 -6
  38. truefoundry/deploy/function_service/remote/context.py +0 -3
  39. truefoundry/deploy/function_service/remote/method.py +0 -67
  40. truefoundry/deploy/function_service/remote/remote.py +0 -144
  41. truefoundry/deploy/function_service/route.py +0 -137
  42. truefoundry/deploy/function_service/service.py +0 -113
  43. truefoundry/deploy/function_service/utils.py +0 -53
  44. truefoundry/langchain/__init__.py +0 -12
  45. truefoundry/langchain/deprecated.py +0 -302
  46. truefoundry/langchain/truefoundry_chat.py +0 -130
  47. truefoundry/langchain/truefoundry_embeddings.py +0 -171
  48. truefoundry/langchain/truefoundry_llm.py +0 -106
  49. truefoundry/langchain/utils.py +0 -44
  50. truefoundry/ml/log_types/artifacts/model_extras.py +0 -48
  51. {truefoundry-0.5.0rc7.dist-info → truefoundry-0.5.1rc2.dist-info}/WHEEL +0 -0
  52. {truefoundry-0.5.0rc7.dist-info → truefoundry-0.5.1rc2.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,8 @@
1
1
  import os
2
2
  import time
3
3
  import uuid
4
- from pathlib import Path
4
+ import zipfile
5
+ from io import BytesIO
5
6
  from typing import (
6
7
  TYPE_CHECKING,
7
8
  Any,
@@ -15,9 +16,8 @@ from typing import (
15
16
  )
16
17
 
17
18
  import coolname
18
- import pandas as pd
19
19
 
20
- from truefoundry.common.utils import relogin_error_message
20
+ from truefoundry.common.utils import ContextualDirectoryManager, relogin_error_message
21
21
  from truefoundry.ml import ModelVersionEnvironment, constants
22
22
  from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
23
23
  ArtifactDto,
@@ -27,12 +27,14 @@ from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
27
27
  CreateRunRequestDto,
28
28
  DatasetDto,
29
29
  ExperimentsApi,
30
+ ExportDeploymentFilesRequestDto,
30
31
  ListArtifactsRequestDto,
31
32
  ListArtifactVersionsRequestDto,
32
33
  ListDatasetsRequestDto,
33
34
  ListModelVersionsRequestDto,
34
35
  MlfoundryArtifactsApi,
35
36
  ModelDto,
37
+ ModelServer,
36
38
  RunsApi,
37
39
  RunTagDto,
38
40
  SearchRunsRequestDto,
@@ -75,7 +77,7 @@ if TYPE_CHECKING:
75
77
  from truefoundry.ml import ModelFrameworkType
76
78
 
77
79
  _SEARCH_MAX_RESULTS_DEFAULT = 1000
78
-
80
+ _ML_FOUNDRY_API_REQUEST_TIMEOUT = 10
79
81
  _INTERNAL_ENV_VARS = [
80
82
  "TFY_INTERNAL_APPLICATION_ID",
81
83
  "TFY_INTERNAL_JOB_RUN_NAME",
@@ -476,43 +478,6 @@ class MlFoundry:
476
478
  )
477
479
  return mlfoundry_run
478
480
 
479
- def get_all_runs(
480
- self,
481
- ml_repo: str,
482
- ) -> pd.DataFrame:
483
- """Returns all the run name and id present under a ML Repo.
484
-
485
- The user must have `READ` access to the ML Repo.
486
-
487
- Args:
488
- ml_repo (str): Name of the ML Repo.
489
- Returns:
490
- pd.DataFrame: dataframe with two columns- run_id and run_name
491
-
492
- Examples:
493
-
494
- ### get all the runs from a ml_repo
495
- ```python
496
- from truefoundry.ml import get_client
497
-
498
- client = get_client()
499
-
500
- run = client.get_all_runs(ml_repo='my-repo')
501
- ```
502
- """
503
- runs = []
504
- for run in self.search_runs(ml_repo=ml_repo):
505
- runs.append((run.run_id, run.run_name))
506
-
507
- if len(runs) == 0:
508
- return pd.DataFrame(
509
- columns=[constants.RUN_ID_COL_NAME, constants.RUN_NAME_COL_NAME]
510
- )
511
-
512
- return pd.DataFrame(
513
- runs, columns=[constants.RUN_ID_COL_NAME, constants.RUN_NAME_COL_NAME]
514
- )
515
-
516
481
  def search_runs(
517
482
  self,
518
483
  ml_repo: str,
@@ -658,6 +623,74 @@ class MlFoundry:
658
623
  """
659
624
  return self._tracking_uri
660
625
 
626
+ def _initialize_model_server(
627
+ self,
628
+ name: str,
629
+ model_version_fqn: str,
630
+ workspace_fqn: str,
631
+ model_server: ModelServer,
632
+ output_dir: Optional[str] = None,
633
+ ) -> str:
634
+ """
635
+ Initialize the model server for deployment.
636
+
637
+ Args:
638
+ name (str): Name of the application.
639
+ model_version_fqn (str): Fully Qualified Name of the model version.
640
+ workspace_fqn (str): Fully Qualified Name of the workspace.
641
+ model_server (ModelServer): Server type for deployment (e.g., TRITON).
642
+ output_dir (Optional[str]): Directory where the model files will be extracted.
643
+ Defaults to the current working directory.
644
+
645
+ Returns:
646
+ str: Path to the directory where the model files are extracted.
647
+
648
+ Raises:
649
+ MlFoundryException: If an error occurs during API calls, directory creation, or file extraction.
650
+ """
651
+
652
+ # Using get_with_http_info to get the response object and extract the raw data
653
+ try:
654
+ export_deployment_files_request_dto = ExportDeploymentFilesRequestDto(
655
+ model_version_fqn=model_version_fqn,
656
+ workspace_fqn=workspace_fqn,
657
+ service_name=name,
658
+ model_server=model_server,
659
+ )
660
+ response = self._mlfoundry_artifacts_api.export_deployment_files_by_fqn_post_with_http_info(
661
+ export_deployment_files_request_dto=export_deployment_files_request_dto,
662
+ _preload_content=False,
663
+ _request_timeout=_ML_FOUNDRY_API_REQUEST_TIMEOUT,
664
+ )
665
+ except ApiException as e:
666
+ err_msg = (
667
+ f"Failed to fetch deployment files for name={name}, "
668
+ f"model_version_fqn={model_version_fqn}, workspace_fqn={workspace_fqn}. "
669
+ f"Error: {e}"
670
+ )
671
+ raise MlFoundryException(err_msg) from e
672
+
673
+ output_dir = os.path.abspath(output_dir or os.getcwd())
674
+ codegen_dir = os.path.join(output_dir, name)
675
+ if codegen_dir == output_dir:
676
+ raise ValueError("Name cannot be empty, please provide a valid name")
677
+
678
+ try:
679
+ with ContextualDirectoryManager(dir_path=codegen_dir) as dir_path:
680
+ with zipfile.ZipFile(BytesIO(response.raw_data), mode="r") as zip_file:
681
+ zip_file.extractall(dir_path)
682
+ except FileExistsError as e:
683
+ err_msg = (
684
+ f"Deployment directory {codegen_dir!r} already exists. "
685
+ "Please choose a different deployment name or delete the existing directory."
686
+ )
687
+ raise MlFoundryException(err_msg) from e
688
+ except zipfile.BadZipFile as e:
689
+ raise MlFoundryException(
690
+ f"Failed to extract model files. Error: {e}"
691
+ ) from e
692
+ return codegen_dir
693
+
661
694
  def get_model_version(
662
695
  self,
663
696
  ml_repo: str,
@@ -1227,7 +1260,6 @@ class MlFoundry:
1227
1260
  ml_repo: str,
1228
1261
  name: str,
1229
1262
  model_file_or_folder: Union[str, BlobStorageDirectory],
1230
- additional_files: Sequence[Tuple[Union[str, Path], Optional[str]]] = (),
1231
1263
  description: Optional[str] = None,
1232
1264
  metadata: Optional[Dict[str, Any]] = None,
1233
1265
  progress: Optional[bool] = None,
@@ -1263,41 +1295,6 @@ class MlFoundry:
1263
1295
  Can also be `None` if the framework is not known or not supported.
1264
1296
  **Deprecated**: Prefer `ModelFrameworkType` over `enums.ModelFramework`.
1265
1297
 
1266
- additional_files (Sequence[Tuple[Union[str, Path], Optional[str]]], optional): A list of pairs
1267
- of (source path, destination path) to add additional files and folders
1268
- to the model version contents. The first member of the pair should be a file or directory path
1269
- and the second member should be the path inside the model versions contents to upload to.
1270
- The model version contents are arranged like follows
1271
- .
1272
- └── model/
1273
- └── # model files are serialized here
1274
- └── # any additional files and folders can be added here.
1275
-
1276
- You can also add additional files to model/ subdirectory by specifying the destination path as model/
1277
-
1278
- ```python
1279
- from truefoundry.ml import TensorFlowFramework
1280
-
1281
- run.log_model(
1282
- name="xyz",
1283
- model_file_or_folder="clf.joblib",
1284
- framework=TensorFlowFramework(),
1285
- additional_files=[("foo.txt", "foo/bar/foo.txt"), ("tokenizer/", "foo/tokenizer/")]
1286
- )
1287
- ```
1288
-
1289
- would result in
1290
-
1291
- ```
1292
- .
1293
- ├── model/
1294
- │ └── clf.joblib # if `model_file_or_folder` is a folder, contents will be added here
1295
- └── foo/
1296
- ├── bar/
1297
- │ └── foo.txt
1298
- └── tokenizer/
1299
- └── # contents of tokenizer/ directory will be uploaded here
1300
- ```
1301
1298
  description (Optional[str], optional): arbitrary text upto 1024 characters to store as description.
1302
1299
  This field can be updated at any time after logging. Defaults to `None`
1303
1300
  metadata (Optional[Dict[str, Any]], optional): arbitrary json serializable dictionary to store metadata.
@@ -1387,7 +1384,6 @@ class MlFoundry:
1387
1384
  ml_repo_id=ml_repo_id,
1388
1385
  name=name,
1389
1386
  model_file_or_folder=model_file_or_folder,
1390
- additional_files=additional_files,
1391
1387
  description=description,
1392
1388
  metadata=metadata,
1393
1389
  step=None,
@@ -3,7 +3,6 @@ import os
3
3
  import platform
4
4
  import re
5
5
  import time
6
- from pathlib import Path
7
6
  from typing import (
8
7
  TYPE_CHECKING,
9
8
  Any,
@@ -12,7 +11,6 @@ from typing import (
12
11
  Iterator,
13
12
  List,
14
13
  Optional,
15
- Sequence,
16
14
  Tuple,
17
15
  Union,
18
16
  )
@@ -930,7 +928,6 @@ class MlFoundryRun:
930
928
  *,
931
929
  name: str,
932
930
  model_file_or_folder: Union[str, BlobStorageDirectory],
933
- additional_files: Sequence[Tuple[Union[str, Path], Optional[str]]] = (),
934
931
  description: Optional[str] = None,
935
932
  metadata: Optional[Dict[str, Any]] = None,
936
933
  step: int = 0,
@@ -965,33 +962,7 @@ class MlFoundryRun:
965
962
  Supported frameworks can be found in `truefoundry.ml.enums.ModelFramework`.
966
963
  Can also be `None` if the framework is not known or not supported.
967
964
  **Deprecated**: Prefer `ModelFrameworkType` over `enums.ModelFramework`.
968
- additional_files (Sequence[Tuple[Union[str, Path], Optional[str]]], optional): A list of pairs
969
- of (source path, destination path) to add additional files and folders
970
- to the model version contents. The first member of the pair should be a file or directory path
971
- and the second member should be the path inside the model versions contents to upload to.
972
- The model version contents are arranged like follows
973
- .
974
- └── model/
975
- └── # model files are serialized here
976
- └── # any additional files and folders can be added here.
977
-
978
- You can also add additional files to model/ subdirectory by specifying the destination path as model/
979
965
 
980
- ```
981
- E.g. >>> run.log_model(
982
- ... name="xyz", model_file_or_folder="clf.joblib", framework="sklearn",
983
- ... additional_files=[("foo.txt", "foo/bar/foo.txt"), ("tokenizer/", "foo/tokenizer/")]
984
- ... )
985
- would result in
986
- .
987
- ├── model/
988
- │ └── clf.joblib # if `model_file_or_folder` is a folder, contents will be added here
989
- └── foo/
990
- ├── bar/
991
- │ └── foo.txt
992
- └── tokenizer/
993
- └── # contents of tokenizer/ directory will be uploaded here
994
- ```
995
966
  description (Optional[str], optional): arbitrary text upto 1024 characters to store as description.
996
967
  This field can be updated at any time after logging. Defaults to `None`
997
968
  metadata (Optional[Dict[str, Any]], optional): arbitrary json serializable dictionary to store metadata.
@@ -1084,7 +1055,6 @@ class MlFoundryRun:
1084
1055
  run=self,
1085
1056
  name=name,
1086
1057
  model_file_or_folder=model_file_or_folder,
1087
- additional_files=additional_files,
1088
1058
  description=description,
1089
1059
  metadata=metadata,
1090
1060
  step=step,
@@ -1,10 +1,63 @@
1
+ import os
1
2
  import warnings
2
- from typing import Any, Dict, Literal, Optional, Union, get_args
3
-
4
- from truefoundry.ml import ModelFramework
3
+ from collections import OrderedDict
4
+ from pickle import load as pickle_load
5
+ from typing import Any, Callable, Dict, List, Literal, Optional, Type, Union, get_args
6
+
7
+ from truefoundry.common.utils import (
8
+ get_python_version_major_minor,
9
+ list_pip_packages_installed,
10
+ )
11
+ from truefoundry.ml.autogen.client import SerializationFormat
5
12
  from truefoundry.ml.autogen.entities import artifacts as autogen_artifacts
13
+ from truefoundry.ml.enums import ModelFramework
14
+ from truefoundry.ml.log_types.artifacts.utils import (
15
+ get_single_file_path_if_only_one_in_directory,
16
+ to_unix_path,
17
+ )
6
18
  from truefoundry.pydantic_v1 import BaseModel, Field
7
19
 
20
+ # Map serialization format to corresponding pip packages
21
+ SERIALIZATION_FORMAT_TO_PACKAGES_NAME_MAP = {
22
+ SerializationFormat.JOBLIB: ["joblib"],
23
+ SerializationFormat.CLOUDPICKLE: ["cloudpickle"],
24
+ }
25
+
26
+
27
+ class _SerializationFormatLoaderRegistry:
28
+ def __init__(self):
29
+ # An OrderedDict is used to maintain the order of loaders based on priority
30
+ # The loaders are added in the following order:
31
+ # 1. joblib (if available)
32
+ # 2. cloudpickle (if available)
33
+ # 3. pickle (default fallback)
34
+ # This ensures that when looking up a loader, it follows the correct loading priority.
35
+ self._loader_map: Dict[SerializationFormat, Callable[[bytes], object]] = (
36
+ OrderedDict()
37
+ )
38
+ try:
39
+ from joblib import load as joblib_load
40
+
41
+ self._loader_map[SerializationFormat.JOBLIB] = joblib_load
42
+ except ImportError:
43
+ pass
44
+
45
+ try:
46
+ from cloudpickle import load as cloudpickle_load
47
+
48
+ self._loader_map[SerializationFormat.CLOUDPICKLE] = cloudpickle_load
49
+ except ImportError:
50
+ pass
51
+
52
+ # Add pickle loader as a fallback
53
+ self._loader_map[SerializationFormat.PICKLE] = pickle_load
54
+
55
+ def get_loader_map(self) -> Dict[SerializationFormat, Callable[[bytes], object]]:
56
+ return self._loader_map
57
+
58
+
59
+ _serialization_format_loader_registry = _SerializationFormatLoaderRegistry()
60
+
8
61
 
9
62
  class FastAIFramework(autogen_artifacts.FastAIFramework):
10
63
  """FastAI model Framework"""
@@ -167,3 +220,204 @@ class _ModelFramework(BaseModel):
167
220
  return None
168
221
 
169
222
  return cls.parse_obj(obj).__root__
223
+
224
+
225
+ # Mapping of model frameworks to pip packages
226
+ _MODEL_FRAMEWORK_TO_PIP_PACKAGES: Dict[Type[ModelFrameworkType], List[str]] = {
227
+ SklearnFramework: ["scikit-learn", "numpy", "pandas"],
228
+ XGBoostFramework: ["xgboost", "numpy", "pandas"],
229
+ }
230
+
231
+
232
+ def _get_required_framework_pip_packages(framework: "ModelFrameworkType") -> List[str]:
233
+ """
234
+ Fetches the pip packages required for a given model framework.
235
+
236
+ Args:
237
+ framework ("ModelFrameworkType"): The model framework for which to fetch the pip packages.
238
+
239
+ Returns:
240
+ List[str]: The list of pip packages required for the given model framework.
241
+ If no packages are found for the framework type, returns an empty list.
242
+ """
243
+ return _MODEL_FRAMEWORK_TO_PIP_PACKAGES.get(framework.__class__, [])
244
+
245
+
246
+ def _detect_model_serialization_format(
247
+ model_file_path: str,
248
+ ) -> Optional[SerializationFormat]:
249
+ """
250
+ The function will attempt to load the model using each framework's loader and return the first successful one.
251
+
252
+ Args:
253
+ model_file_path (str): The path to the file to be loaded.
254
+
255
+ Returns:
256
+ Optional[SerializationFormat]: The serialization format if successfully loaded, None otherwise.
257
+ """
258
+ # Attempt to load the model using each framework
259
+ for (
260
+ serialization_format,
261
+ loader,
262
+ ) in _serialization_format_loader_registry.get_loader_map().items():
263
+ try:
264
+ with open(model_file_path, "rb") as f:
265
+ loader(f)
266
+ return serialization_format
267
+ except Exception:
268
+ continue
269
+ return None
270
+
271
+
272
+ def _fetch_framework_specific_pip_packages(
273
+ framework: "ModelFrameworkType",
274
+ ) -> List[str]:
275
+ """
276
+ Fetch the pip packages required for the given framework, including any dependencies
277
+ related to the framework's serialization format.
278
+
279
+ Args:
280
+ framework: The framework object (e.g., SklearnFramework, XGBoostFramework).
281
+
282
+ Returns:
283
+ List[str]: A list of pip packages for the given framework and environment,
284
+ including any dependencies based on the serialization format
285
+ (e.g., ['numpy==1.19.5', ...]).
286
+ """
287
+ framework_package_names = _get_required_framework_pip_packages(framework=framework)
288
+
289
+ # Add serialization format dependencies if applicable
290
+ if isinstance(framework, (SklearnFramework, XGBoostFramework)):
291
+ framework_package_names.extend(
292
+ SERIALIZATION_FORMAT_TO_PACKAGES_NAME_MAP.get(
293
+ framework.serialization_format, []
294
+ )
295
+ )
296
+ return [
297
+ f"{package.name}=={package.version}"
298
+ for package in list_pip_packages_installed(
299
+ filter_package_names=framework_package_names
300
+ )
301
+ ]
302
+
303
+
304
+ def auto_update_environment_details(
305
+ environment: autogen_artifacts.ModelVersionEnvironment,
306
+ framework: Optional[ModelFrameworkType],
307
+ ):
308
+ """
309
+ Auto fetch the environment details if not provided, based on the provided environment and framework.
310
+
311
+ Args:
312
+ environment: The environment object that holds environment details like python_version and pip_packages.
313
+ framework: The framework object (e.g., SklearnFramework, XGBoostFramework) that may affect pip_package fetching.
314
+ """
315
+ # Auto fetch python_version if not provided
316
+ if not environment.python_version:
317
+ environment.python_version = get_python_version_major_minor()
318
+
319
+ # Framework-specific pip_package handling
320
+ if framework and not environment.pip_packages:
321
+ environment.pip_packages = _fetch_framework_specific_pip_packages(framework)
322
+
323
+
324
+ def _validate_and_get_absolute_model_filepath(
325
+ model_file_or_folder: str,
326
+ model_filepath: Optional[str] = None,
327
+ ) -> Optional[str]:
328
+ # If no model_filepath is set, resolve it from the directory
329
+ if not model_filepath:
330
+ # If model_filepath is not set, resolve it based on these cases:
331
+ # - Case 1: model_file_or_folder/model.joblib -> model.joblib
332
+ # - Case 2: model_file_or_folder/folder/model.joblib -> folder/model.joblib
333
+ # - Case 3: model_file_or_folder/folder/model.joblib, model_file_or_folder/config.json -> None
334
+ return get_single_file_path_if_only_one_in_directory(model_file_or_folder)
335
+
336
+ # If model_filepath is already set, validate and resolve it:
337
+ # - Case 1: Resolve the absolute file path of the model file relative to the provided directory.
338
+ # Example: If model_file_or_folder is '/root/models' and model_filepath is 'model.joblib',
339
+ # the resolved model file path would be '/root/models/model.joblib'. Validate it.
340
+ #
341
+ # - Case 2: If model_filepath is a relative path, resolve it to an absolute path based on the provided directory.
342
+ # Example: If model_file_or_folder is '/root/models' and model_filepath is 'subfolder/model.joblib',
343
+ # the resolved path would be '/root/models/subfolder/model.joblib'. Validate it.
344
+ #
345
+ # - Case 3: Verify that the resolved model file exists and is a valid file.
346
+ # Example: If the resolved path is '/root/models/model.joblib', check if the file exists.
347
+ # If it does not exist, raise a FileNotFoundError.
348
+ #
349
+ # - Case 4: Ensure the resolved model file is located within the specified directory or is the directory itself.
350
+ # Example: If the resolved path is '/root/models/model.joblib' and model_file_or_folder is '/root/models',
351
+ # the resolved path is valid. If the file lies outside '/root/models', raise a ValueError.
352
+ #
353
+
354
+ # If model_filepath is set, Resolve the absolute path of the model file (It can be a relative path or absolute path)
355
+ model_dir = (
356
+ os.path.dirname(model_file_or_folder)
357
+ if os.path.isfile(model_file_or_folder)
358
+ else model_file_or_folder
359
+ )
360
+ absolute_model_filepath = os.path.abspath(os.path.join(model_dir, model_filepath))
361
+
362
+ # Validate if resolve valid is within the provided directory or is the same as it
363
+ if not (
364
+ absolute_model_filepath == model_file_or_folder
365
+ or absolute_model_filepath.startswith(model_file_or_folder + os.sep)
366
+ ):
367
+ raise ValueError(
368
+ f"model_filepath '{model_filepath}' must be relative to "
369
+ f"{model_file_or_folder}. Resolved path '{absolute_model_filepath}' is invalid."
370
+ )
371
+
372
+ if not os.path.isfile(absolute_model_filepath):
373
+ raise FileNotFoundError(f"Model file not found: {absolute_model_filepath}")
374
+
375
+ return absolute_model_filepath
376
+
377
+
378
+ def _validate_and_resolve_model_filepath(
379
+ model_file_or_folder: str,
380
+ model_filepath: Optional[str] = None,
381
+ ) -> Optional[str]:
382
+ absolute_model_filepath = _validate_and_get_absolute_model_filepath(
383
+ model_file_or_folder=model_file_or_folder, model_filepath=model_filepath
384
+ )
385
+ if absolute_model_filepath:
386
+ return to_unix_path(
387
+ os.path.relpath(absolute_model_filepath, model_file_or_folder)
388
+ if os.path.isdir(model_file_or_folder)
389
+ else os.path.basename(absolute_model_filepath)
390
+ )
391
+
392
+
393
+ def auto_update_model_framework_details(
394
+ framework: "ModelFrameworkType", model_file_or_folder: str
395
+ ):
396
+ """
397
+ Auto update the model framework details based on the provided model file or folder path.
398
+
399
+ Args:
400
+ framework: The framework object (e.g., SklearnFramework, XGBoostFramework) to update.
401
+ model_file_or_folder: The path to the model file or folder.
402
+ """
403
+
404
+ # Ensure the model file or folder path is an absolute path
405
+ model_file_or_folder = os.path.abspath(model_file_or_folder)
406
+
407
+ if isinstance(framework, (SklearnFramework, XGBoostFramework)):
408
+ framework.model_filepath = _validate_and_resolve_model_filepath(
409
+ model_file_or_folder=model_file_or_folder,
410
+ model_filepath=framework.model_filepath,
411
+ )
412
+ if framework.model_filepath:
413
+ absolute_model_filepath = (
414
+ model_file_or_folder
415
+ if os.path.isfile(model_file_or_folder)
416
+ else os.path.join(model_file_or_folder, framework.model_filepath)
417
+ )
418
+ framework.serialization_format = (
419
+ framework.serialization_format
420
+ or _detect_model_serialization_format(
421
+ model_file_path=absolute_model_filepath
422
+ )
423
+ )
@@ -29,6 +29,8 @@ _ML_REPO_NAME_REGEX = re.compile(r"^[a-zA-Z][a-zA-Z0-9\-]{1,98}[a-zA-Z0-9]$")
29
29
  _RUN_NAME_REGEX = re.compile(r"^[a-zA-Z0-9-]*$")
30
30
  _RUN_LOG_LOG_TYPE_REGEX = re.compile(r"^[a-zA-Z0-9-/]*$")
31
31
  _RUN_LOG_KEY_REGEX = re.compile(r"^[a-zA-Z0-9-_]*$")
32
+ _APP_NAME_REGEX = re.compile(r"^[a-z][a-z0-9\\-]{1,30}[a-z0-9]$")
33
+
32
34
 
33
35
  MAX_PARAMS_TAGS_PER_BATCH = 100
34
36
  MAX_METRICS_PER_BATCH = 1000
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: truefoundry
3
- Version: 0.5.0rc7
3
+ Version: 0.5.1rc2
4
4
  Summary: Truefoundry CLI
5
5
  Author: Abhishek Choudhary
6
6
  Author-email: abhishek@truefoundry.com
@@ -19,7 +19,6 @@ Requires-Dist: aenum (>=3.0.0,<4.0.0)
19
19
  Requires-Dist: click (>=7.0.0,<9.0.0)
20
20
  Requires-Dist: coolname (>=1.1.0,<2.0.0)
21
21
  Requires-Dist: docker (>=6.1.2,<8.0.0)
22
- Requires-Dist: fastapi (>=0.56.0,<0.200.0)
23
22
  Requires-Dist: filelock (>=3.8.0,<4.0.0)
24
23
  Requires-Dist: flytekit (==1.13.13) ; extra == "workflow"
25
24
  Requires-Dist: gitignorefile (>=1.1.2,<2.0.0)
@@ -29,8 +28,6 @@ Requires-Dist: numpy (>=1.23.0,<2.0.0) ; python_version < "3.12"
29
28
  Requires-Dist: numpy (>=1.26.0,<2.0.0) ; python_version >= "3.12"
30
29
  Requires-Dist: openai (>=1.16.2,<2.0.0)
31
30
  Requires-Dist: packaging (>=20.0,<25.0)
32
- Requires-Dist: pandas (>=1.0.0,<3.0.0) ; python_version < "3.10"
33
- Requires-Dist: pandas (>=1.4.0,<3.0.0) ; python_version >= "3.10"
34
31
  Requires-Dist: pydantic (>=1.8.2,<3.0.0)
35
32
  Requires-Dist: pygments (>=2.12.0,<3.0.0)
36
33
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
@@ -46,7 +43,6 @@ Requires-Dist: scipy (>=1.5.0,<2.0.0) ; python_version < "3.12"
46
43
  Requires-Dist: tqdm (>=4.0.0,<5.0.0)
47
44
  Requires-Dist: typing-extensions (>=4.0)
48
45
  Requires-Dist: urllib3 (>=1.26.18,<3)
49
- Requires-Dist: uvicorn (>=0.13.0,<1.0.0)
50
46
  Requires-Dist: yq (>=3.1.0,<4.0.0)
51
47
  Description-Content-Type: text/markdown
52
48