truefoundry 0.3.4rc1__py3-none-any.whl → 0.4.0__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 (253) hide show
  1. truefoundry/__init__.py +2 -0
  2. truefoundry/autodeploy/agents/developer.py +1 -1
  3. truefoundry/autodeploy/agents/project_identifier.py +2 -2
  4. truefoundry/autodeploy/agents/tester.py +1 -1
  5. truefoundry/autodeploy/cli.py +1 -1
  6. truefoundry/autodeploy/tools/list_files.py +1 -1
  7. truefoundry/cli/__main__.py +3 -17
  8. truefoundry/common/__init__.py +0 -0
  9. truefoundry/{deploy/lib/auth → common}/auth_service_client.py +50 -40
  10. truefoundry/common/constants.py +12 -0
  11. truefoundry/{deploy/lib/auth → common}/credential_file_manager.py +7 -7
  12. truefoundry/{deploy/lib/auth → common}/credential_provider.py +10 -23
  13. truefoundry/common/entities.py +124 -0
  14. truefoundry/common/exceptions.py +12 -0
  15. truefoundry/common/request_utils.py +84 -0
  16. truefoundry/common/servicefoundry_client.py +91 -0
  17. truefoundry/common/utils.py +56 -0
  18. truefoundry/deploy/auto_gen/models.py +4 -6
  19. truefoundry/deploy/cli/cli.py +3 -1
  20. truefoundry/deploy/cli/commands/apply_command.py +1 -1
  21. truefoundry/deploy/cli/commands/build_command.py +1 -1
  22. truefoundry/deploy/cli/commands/deploy_command.py +1 -1
  23. truefoundry/deploy/cli/commands/login_command.py +2 -2
  24. truefoundry/deploy/cli/commands/patch_application_command.py +1 -1
  25. truefoundry/deploy/cli/commands/patch_command.py +1 -1
  26. truefoundry/deploy/cli/commands/terminate_comand.py +1 -1
  27. truefoundry/deploy/cli/util.py +1 -1
  28. truefoundry/deploy/function_service/remote/remote.py +1 -1
  29. truefoundry/deploy/lib/auth/servicefoundry_session.py +2 -2
  30. truefoundry/deploy/lib/clients/servicefoundry_client.py +120 -159
  31. truefoundry/deploy/lib/const.py +1 -35
  32. truefoundry/deploy/lib/exceptions.py +0 -16
  33. truefoundry/deploy/lib/model/entity.py +1 -112
  34. truefoundry/deploy/lib/session.py +14 -42
  35. truefoundry/deploy/lib/util.py +0 -37
  36. truefoundry/{python_deploy_codegen.py → deploy/python_deploy_codegen.py} +2 -2
  37. truefoundry/deploy/v2/lib/deploy.py +3 -3
  38. truefoundry/deploy/v2/lib/deployable_patched_models.py +1 -1
  39. truefoundry/langchain/truefoundry_chat.py +1 -1
  40. truefoundry/langchain/truefoundry_embeddings.py +1 -1
  41. truefoundry/langchain/truefoundry_llm.py +1 -1
  42. truefoundry/langchain/utils.py +0 -41
  43. truefoundry/ml/__init__.py +37 -6
  44. truefoundry/ml/artifact/__init__.py +0 -0
  45. truefoundry/ml/artifact/truefoundry_artifact_repo.py +1161 -0
  46. truefoundry/ml/autogen/__init__.py +0 -0
  47. truefoundry/ml/autogen/client/__init__.py +370 -0
  48. truefoundry/ml/autogen/client/api/__init__.py +16 -0
  49. truefoundry/ml/autogen/client/api/auth_api.py +184 -0
  50. truefoundry/ml/autogen/client/api/deprecated_api.py +605 -0
  51. truefoundry/ml/autogen/client/api/experiments_api.py +1944 -0
  52. truefoundry/ml/autogen/client/api/health_api.py +299 -0
  53. truefoundry/ml/autogen/client/api/metrics_api.py +371 -0
  54. truefoundry/ml/autogen/client/api/mlfoundry_artifacts_api.py +7213 -0
  55. truefoundry/ml/autogen/client/api/python_deployment_config_api.py +201 -0
  56. truefoundry/ml/autogen/client/api/run_artifacts_api.py +231 -0
  57. truefoundry/ml/autogen/client/api/runs_api.py +2919 -0
  58. truefoundry/ml/autogen/client/api_client.py +822 -0
  59. truefoundry/ml/autogen/client/api_response.py +30 -0
  60. truefoundry/ml/autogen/client/configuration.py +489 -0
  61. truefoundry/ml/autogen/client/exceptions.py +161 -0
  62. truefoundry/ml/autogen/client/models/__init__.py +341 -0
  63. truefoundry/ml/autogen/client/models/add_custom_metrics_to_model_version_request_dto.py +69 -0
  64. truefoundry/ml/autogen/client/models/add_features_to_model_version_request_dto.py +83 -0
  65. truefoundry/ml/autogen/client/models/agent.py +125 -0
  66. truefoundry/ml/autogen/client/models/agent_app.py +118 -0
  67. truefoundry/ml/autogen/client/models/agent_open_api_tool.py +143 -0
  68. truefoundry/ml/autogen/client/models/agent_open_api_tool_with_fqn.py +144 -0
  69. truefoundry/ml/autogen/client/models/agent_with_fqn.py +127 -0
  70. truefoundry/ml/autogen/client/models/artifact_dto.py +115 -0
  71. truefoundry/ml/autogen/client/models/artifact_response_dto.py +75 -0
  72. truefoundry/ml/autogen/client/models/artifact_type.py +39 -0
  73. truefoundry/ml/autogen/client/models/artifact_version_dto.py +141 -0
  74. truefoundry/ml/autogen/client/models/artifact_version_response_dto.py +77 -0
  75. truefoundry/ml/autogen/client/models/artifact_version_status.py +35 -0
  76. truefoundry/ml/autogen/client/models/assistant_message.py +89 -0
  77. truefoundry/ml/autogen/client/models/authorize_user_for_model_request_dto.py +69 -0
  78. truefoundry/ml/autogen/client/models/authorize_user_for_model_version_request_dto.py +69 -0
  79. truefoundry/ml/autogen/client/models/blob_storage_reference.py +93 -0
  80. truefoundry/ml/autogen/client/models/body_get_search_runs_get.py +72 -0
  81. truefoundry/ml/autogen/client/models/chat_prompt.py +156 -0
  82. truefoundry/ml/autogen/client/models/chat_prompt_messages_inner.py +171 -0
  83. truefoundry/ml/autogen/client/models/columns_dto.py +73 -0
  84. truefoundry/ml/autogen/client/models/content.py +153 -0
  85. truefoundry/ml/autogen/client/models/content1.py +153 -0
  86. truefoundry/ml/autogen/client/models/content2.py +174 -0
  87. truefoundry/ml/autogen/client/models/content2_any_of_inner.py +150 -0
  88. truefoundry/ml/autogen/client/models/create_artifact_request_dto.py +74 -0
  89. truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +65 -0
  90. truefoundry/ml/autogen/client/models/create_artifact_version_request_dto.py +74 -0
  91. truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +65 -0
  92. truefoundry/ml/autogen/client/models/create_dataset_request_dto.py +76 -0
  93. truefoundry/ml/autogen/client/models/create_experiment_request_dto.py +94 -0
  94. truefoundry/ml/autogen/client/models/create_experiment_response_dto.py +67 -0
  95. truefoundry/ml/autogen/client/models/create_model_version_request_dto.py +95 -0
  96. truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_request_dto.py +73 -0
  97. truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_response_dto.py +79 -0
  98. truefoundry/ml/autogen/client/models/create_multi_part_upload_request_dto.py +73 -0
  99. truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +72 -0
  100. truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +67 -0
  101. truefoundry/ml/autogen/client/models/create_run_request_dto.py +97 -0
  102. truefoundry/ml/autogen/client/models/create_run_response_dto.py +75 -0
  103. truefoundry/ml/autogen/client/models/dataset_dto.py +112 -0
  104. truefoundry/ml/autogen/client/models/dataset_response_dto.py +75 -0
  105. truefoundry/ml/autogen/client/models/delete_artifact_versions_request_dto.py +65 -0
  106. truefoundry/ml/autogen/client/models/delete_dataset_request_dto.py +74 -0
  107. truefoundry/ml/autogen/client/models/delete_model_version_request_dto.py +65 -0
  108. truefoundry/ml/autogen/client/models/delete_run_request.py +65 -0
  109. truefoundry/ml/autogen/client/models/delete_tag_request_dto.py +68 -0
  110. truefoundry/ml/autogen/client/models/experiment_dto.py +127 -0
  111. truefoundry/ml/autogen/client/models/experiment_id_request_dto.py +67 -0
  112. truefoundry/ml/autogen/client/models/experiment_response_dto.py +75 -0
  113. truefoundry/ml/autogen/client/models/experiment_tag_dto.py +69 -0
  114. truefoundry/ml/autogen/client/models/feature_dto.py +68 -0
  115. truefoundry/ml/autogen/client/models/feature_value_type.py +35 -0
  116. truefoundry/ml/autogen/client/models/file_info_dto.py +76 -0
  117. truefoundry/ml/autogen/client/models/finalize_artifact_version_request_dto.py +101 -0
  118. truefoundry/ml/autogen/client/models/get_experiment_response_dto.py +88 -0
  119. truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +75 -0
  120. truefoundry/ml/autogen/client/models/get_metric_history_response.py +79 -0
  121. truefoundry/ml/autogen/client/models/get_signed_url_for_dataset_write_request_dto.py +68 -0
  122. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_request_dto.py +68 -0
  123. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_response_dto.py +81 -0
  124. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_request_dto.py +69 -0
  125. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_response_dto.py +83 -0
  126. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_request_dto.py +68 -0
  127. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_response_dto.py +81 -0
  128. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_write_response_dto.py +81 -0
  129. truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +73 -0
  130. truefoundry/ml/autogen/client/models/http_validation_error.py +82 -0
  131. truefoundry/ml/autogen/client/models/image_content_part.py +87 -0
  132. truefoundry/ml/autogen/client/models/image_url.py +75 -0
  133. truefoundry/ml/autogen/client/models/internal_metadata.py +180 -0
  134. truefoundry/ml/autogen/client/models/latest_run_log_dto.py +78 -0
  135. truefoundry/ml/autogen/client/models/list_artifact_versions_request_dto.py +107 -0
  136. truefoundry/ml/autogen/client/models/list_artifact_versions_response_dto.py +87 -0
  137. truefoundry/ml/autogen/client/models/list_artifacts_request_dto.py +96 -0
  138. truefoundry/ml/autogen/client/models/list_artifacts_response_dto.py +86 -0
  139. truefoundry/ml/autogen/client/models/list_colums_response_dto.py +75 -0
  140. truefoundry/ml/autogen/client/models/list_datasets_request_dto.py +78 -0
  141. truefoundry/ml/autogen/client/models/list_datasets_response_dto.py +86 -0
  142. truefoundry/ml/autogen/client/models/list_experiments_response_dto.py +86 -0
  143. truefoundry/ml/autogen/client/models/list_files_for_artifact_version_request_dto.py +76 -0
  144. truefoundry/ml/autogen/client/models/list_files_for_artifact_versions_response_dto.py +82 -0
  145. truefoundry/ml/autogen/client/models/list_files_for_dataset_request_dto.py +76 -0
  146. truefoundry/ml/autogen/client/models/list_files_for_dataset_response_dto.py +82 -0
  147. truefoundry/ml/autogen/client/models/list_latest_run_logs_response_dto.py +82 -0
  148. truefoundry/ml/autogen/client/models/list_metric_history_request_dto.py +69 -0
  149. truefoundry/ml/autogen/client/models/list_metric_history_response_dto.py +84 -0
  150. truefoundry/ml/autogen/client/models/list_model_version_response_dto.py +87 -0
  151. truefoundry/ml/autogen/client/models/list_model_versions_request_dto.py +93 -0
  152. truefoundry/ml/autogen/client/models/list_models_request_dto.py +89 -0
  153. truefoundry/ml/autogen/client/models/list_models_response_dto.py +84 -0
  154. truefoundry/ml/autogen/client/models/list_run_artifacts_response_dto.py +84 -0
  155. truefoundry/ml/autogen/client/models/list_run_logs_response_dto.py +82 -0
  156. truefoundry/ml/autogen/client/models/list_seed_experiments_response_dto.py +81 -0
  157. truefoundry/ml/autogen/client/models/log_batch_request_dto.py +106 -0
  158. truefoundry/ml/autogen/client/models/log_metric_request_dto.py +80 -0
  159. truefoundry/ml/autogen/client/models/log_param_request_dto.py +76 -0
  160. truefoundry/ml/autogen/client/models/method.py +37 -0
  161. truefoundry/ml/autogen/client/models/metric_collection_dto.py +82 -0
  162. truefoundry/ml/autogen/client/models/metric_dto.py +76 -0
  163. truefoundry/ml/autogen/client/models/mime_type.py +37 -0
  164. truefoundry/ml/autogen/client/models/model_configuration.py +103 -0
  165. truefoundry/ml/autogen/client/models/model_dto.py +122 -0
  166. truefoundry/ml/autogen/client/models/model_response_dto.py +75 -0
  167. truefoundry/ml/autogen/client/models/model_schema_dto.py +85 -0
  168. truefoundry/ml/autogen/client/models/model_version_dto.py +170 -0
  169. truefoundry/ml/autogen/client/models/model_version_response_dto.py +75 -0
  170. truefoundry/ml/autogen/client/models/multi_part_upload_dto.py +107 -0
  171. truefoundry/ml/autogen/client/models/multi_part_upload_response_dto.py +79 -0
  172. truefoundry/ml/autogen/client/models/multi_part_upload_storage_provider.py +34 -0
  173. truefoundry/ml/autogen/client/models/notify_artifact_version_failure_dto.py +65 -0
  174. truefoundry/ml/autogen/client/models/openapi_spec.py +152 -0
  175. truefoundry/ml/autogen/client/models/param_dto.py +66 -0
  176. truefoundry/ml/autogen/client/models/parameters.py +84 -0
  177. truefoundry/ml/autogen/client/models/prediction_type.py +34 -0
  178. truefoundry/ml/autogen/client/models/resolve_agent_app_response_dto.py +75 -0
  179. truefoundry/ml/autogen/client/models/restore_run_request_dto.py +65 -0
  180. truefoundry/ml/autogen/client/models/run_data_dto.py +104 -0
  181. truefoundry/ml/autogen/client/models/run_dto.py +84 -0
  182. truefoundry/ml/autogen/client/models/run_info_dto.py +105 -0
  183. truefoundry/ml/autogen/client/models/run_log_dto.py +90 -0
  184. truefoundry/ml/autogen/client/models/run_log_input_dto.py +80 -0
  185. truefoundry/ml/autogen/client/models/run_response_dto.py +75 -0
  186. truefoundry/ml/autogen/client/models/run_tag_dto.py +66 -0
  187. truefoundry/ml/autogen/client/models/search_runs_request_dto.py +94 -0
  188. truefoundry/ml/autogen/client/models/search_runs_response_dto.py +84 -0
  189. truefoundry/ml/autogen/client/models/set_experiment_tag_request_dto.py +73 -0
  190. truefoundry/ml/autogen/client/models/set_tag_request_dto.py +76 -0
  191. truefoundry/ml/autogen/client/models/signed_url_dto.py +69 -0
  192. truefoundry/ml/autogen/client/models/stop.py +152 -0
  193. truefoundry/ml/autogen/client/models/store_run_logs_request_dto.py +83 -0
  194. truefoundry/ml/autogen/client/models/system_message.py +89 -0
  195. truefoundry/ml/autogen/client/models/text.py +153 -0
  196. truefoundry/ml/autogen/client/models/text_content_part.py +84 -0
  197. truefoundry/ml/autogen/client/models/update_artifact_version_request_dto.py +74 -0
  198. truefoundry/ml/autogen/client/models/update_dataset_request_dto.py +74 -0
  199. truefoundry/ml/autogen/client/models/update_experiment_request_dto.py +74 -0
  200. truefoundry/ml/autogen/client/models/update_model_version_request_dto.py +93 -0
  201. truefoundry/ml/autogen/client/models/update_run_request_dto.py +78 -0
  202. truefoundry/ml/autogen/client/models/update_run_response_dto.py +75 -0
  203. truefoundry/ml/autogen/client/models/url.py +153 -0
  204. truefoundry/ml/autogen/client/models/user_message.py +89 -0
  205. truefoundry/ml/autogen/client/models/validation_error.py +87 -0
  206. truefoundry/ml/autogen/client/models/validation_error_loc_inner.py +154 -0
  207. truefoundry/ml/autogen/client/rest.py +426 -0
  208. truefoundry/ml/autogen/client_README.md +320 -0
  209. truefoundry/ml/cli/__init__.py +0 -0
  210. truefoundry/ml/cli/cli.py +18 -0
  211. truefoundry/ml/cli/commands/__init__.py +3 -0
  212. truefoundry/ml/cli/commands/download.py +87 -0
  213. truefoundry/ml/clients/__init__.py +0 -0
  214. truefoundry/ml/clients/entities.py +8 -0
  215. truefoundry/ml/clients/servicefoundry_client.py +45 -0
  216. truefoundry/ml/clients/utils.py +122 -0
  217. truefoundry/ml/constants.py +84 -0
  218. truefoundry/ml/entities.py +62 -0
  219. truefoundry/ml/enums.py +70 -0
  220. truefoundry/ml/env_vars.py +9 -0
  221. truefoundry/ml/exceptions.py +8 -0
  222. truefoundry/ml/git_info.py +60 -0
  223. truefoundry/ml/internal_namespace.py +52 -0
  224. truefoundry/ml/log_types/__init__.py +4 -0
  225. truefoundry/ml/log_types/artifacts/artifact.py +431 -0
  226. truefoundry/ml/log_types/artifacts/constants.py +33 -0
  227. truefoundry/ml/log_types/artifacts/dataset.py +384 -0
  228. truefoundry/ml/log_types/artifacts/general_artifact.py +110 -0
  229. truefoundry/ml/log_types/artifacts/model.py +611 -0
  230. truefoundry/ml/log_types/artifacts/model_extras.py +48 -0
  231. truefoundry/ml/log_types/artifacts/utils.py +161 -0
  232. truefoundry/ml/log_types/image/__init__.py +3 -0
  233. truefoundry/ml/log_types/image/constants.py +8 -0
  234. truefoundry/ml/log_types/image/image.py +357 -0
  235. truefoundry/ml/log_types/image/image_normalizer.py +102 -0
  236. truefoundry/ml/log_types/image/types.py +68 -0
  237. truefoundry/ml/log_types/plot.py +281 -0
  238. truefoundry/ml/log_types/pydantic_base.py +10 -0
  239. truefoundry/ml/log_types/utils.py +12 -0
  240. truefoundry/ml/logger.py +17 -0
  241. truefoundry/ml/mlfoundry_api.py +1575 -0
  242. truefoundry/ml/mlfoundry_run.py +1203 -0
  243. truefoundry/ml/run_utils.py +93 -0
  244. truefoundry/ml/session.py +168 -0
  245. truefoundry/ml/validation_utils.py +346 -0
  246. truefoundry/pydantic_v1.py +8 -1
  247. truefoundry/workflow/__init__.py +16 -1
  248. {truefoundry-0.3.4rc1.dist-info → truefoundry-0.4.0.dist-info}/METADATA +21 -14
  249. truefoundry-0.4.0.dist-info/RECORD +344 -0
  250. truefoundry/deploy/lib/clients/utils.py +0 -41
  251. truefoundry-0.3.4rc1.dist-info/RECORD +0 -136
  252. {truefoundry-0.3.4rc1.dist-info → truefoundry-0.4.0.dist-info}/WHEEL +0 -0
  253. {truefoundry-0.3.4rc1.dist-info → truefoundry-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,161 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import posixpath
5
+ import shutil
6
+ import tempfile
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional, Sequence, Tuple, Union
9
+
10
+ from truefoundry.ml.exceptions import MlFoundryException
11
+ from truefoundry.ml.log_types.artifacts.constants import DESCRIPTION_MAX_LENGTH
12
+
13
+ logger = logging.getLogger("truefoundry.ml")
14
+
15
+
16
+ def _copy_tree(src_path, dest_path, symlinks=False, ignore_dangling_symlinks=False):
17
+ os.makedirs(dest_path, exist_ok=True)
18
+ for item in os.listdir(src_path):
19
+ src = os.path.join(src_path, item)
20
+ dest = os.path.join(dest_path, item)
21
+ if os.path.isdir(src):
22
+ _copy_tree(
23
+ src,
24
+ dest,
25
+ symlinks=symlinks,
26
+ ignore_dangling_symlinks=ignore_dangling_symlinks,
27
+ )
28
+ else:
29
+ shutil.copy2(src, dest, follow_symlinks=True)
30
+
31
+
32
+ def is_destination_path_dirlike(dest_path) -> bool:
33
+ if not dest_path:
34
+ return True
35
+
36
+ if dest_path.endswith(os.sep) or dest_path.endswith(posixpath.sep):
37
+ return True
38
+
39
+ if os.path.exists(dest_path) and os.path.isdir(dest_path):
40
+ return True
41
+
42
+ return False
43
+
44
+
45
+ def _copy_additional_files(
46
+ root_dir: str,
47
+ files_dir: str, # relative to root dir e.g. "files/"
48
+ model_dir: Optional[str], # relative to files_dir e.g "model/"
49
+ additional_files: Sequence[Tuple[Union[str, Path], Optional[str]]],
50
+ ignore_model_dir_dest_conflict: bool = False,
51
+ ):
52
+ """
53
+
54
+ File copying examples:
55
+ # non ambiguous
56
+ # a.txt -> /tmp/ result /tmp/a.txt
57
+ # a.txt -> /tmp/a/ result /tmp/a/a.txt
58
+ # a.txt -> /tmp/a/b/c/d.txt result /tmp/a/b/c/d.txt
59
+ # .gitignore -> /tmp/.gitignore result /tmp/.gitignore
60
+
61
+ # ambiguous but destination directory exists
62
+ # a.txt -> /tmp result /tmp/a.txt
63
+ # a.txt -> /tmp/a (and /tmp/a/ exists) result /tmp/a/a.txt
64
+
65
+ # ambiguous - when the destination can't be reliably distinguished as a directory
66
+ # a -> /tmp/a result /tmp/a
67
+ # a -> /tmp/b result /tmp/b
68
+ # a -> /tmp/a.txt result /tmp/a.txt
69
+ # .gitignore -> /tmp/.gitinclude result /tmp/.gitinclude
70
+ # a.txt -> /tmp/a result /tmp/a
71
+ """
72
+ for src_path, dest_path in additional_files:
73
+ src_path = str(src_path)
74
+ if not os.path.exists(src_path):
75
+ raise MlFoundryException(
76
+ f"Source path {src_path!r} in `additional_files` does not exist."
77
+ )
78
+ dest_path = dest_path or ""
79
+ normalized_path = os.path.normpath(dest_path)
80
+ if dest_path.endswith(os.sep) or dest_path.endswith(posixpath.sep):
81
+ normalized_path += os.sep
82
+ dest_path = normalized_path.lstrip(os.sep)
83
+
84
+ if (
85
+ model_dir
86
+ and dest_path.startswith(model_dir)
87
+ and not ignore_model_dir_dest_conflict
88
+ ):
89
+ logger.warning(
90
+ f"Destination path {dest_path!r} in `additional_files` conflicts with "
91
+ f"reserved path {model_dir!r}/ which is being used to store the model. "
92
+ f"This might cause errors"
93
+ )
94
+
95
+ files_abs_dir = os.path.join(root_dir, files_dir)
96
+ dest_abs_path = os.path.join(files_abs_dir, dest_path)
97
+
98
+ if os.path.isfile(src_path):
99
+ _src = src_path
100
+ if is_destination_path_dirlike(dest_abs_path):
101
+ os.makedirs(dest_abs_path, exist_ok=True)
102
+ _dst = os.path.relpath(
103
+ os.path.join(dest_abs_path, os.path.basename(_src)), files_abs_dir
104
+ )
105
+ else:
106
+ os.makedirs(os.path.dirname(dest_abs_path), exist_ok=True)
107
+ _dst = os.path.relpath(dest_abs_path, files_abs_dir)
108
+ logger.info(f"Adding file {_src} as /{_dst}")
109
+ shutil.copy2(src_path, dest_abs_path, follow_symlinks=True)
110
+ elif os.path.isdir(src_path):
111
+ os.makedirs(dest_abs_path, exist_ok=True)
112
+ _src = src_path.rstrip("/")
113
+ _dst = os.path.relpath(dest_abs_path, files_abs_dir).rstrip("/")
114
+ logger.info(f"Adding contents of {_src}/ to /{_dst}/")
115
+ _copy_tree(
116
+ src_path=src_path,
117
+ dest_path=dest_abs_path,
118
+ symlinks=True,
119
+ ignore_dangling_symlinks=False,
120
+ )
121
+
122
+
123
+ def _validate_description(description: Optional[str]):
124
+ if description is not None:
125
+ if not isinstance(description, str):
126
+ raise MlFoundryException(
127
+ "`description` must be either `None` or type `str`"
128
+ )
129
+ if len(description) > DESCRIPTION_MAX_LENGTH:
130
+ raise MlFoundryException(
131
+ f"`description` cannot be longer than {DESCRIPTION_MAX_LENGTH} characters"
132
+ )
133
+
134
+
135
+ def _validate_artifact_metadata(metadata: Dict[str, Any]):
136
+ if not isinstance(metadata, dict):
137
+ raise MlFoundryException("`metadata` must be json serializable dict")
138
+ try:
139
+ json.dumps(metadata)
140
+ except ValueError as ve:
141
+ raise MlFoundryException("`metadata` must be json serializable dict") from ve
142
+
143
+
144
+ def calculate_local_directory_size(
145
+ directory: tempfile.TemporaryDirectory, # type: ignore[type-arg]
146
+ ):
147
+ """
148
+ Tells about the size of the artifact
149
+
150
+ Args:
151
+ directory (str): directory path
152
+
153
+ Returns:
154
+ total size of the artifact
155
+ """
156
+ total_size = 0
157
+ for path, _dirs, files in os.walk(directory.name):
158
+ for f in files:
159
+ file_path = os.path.join(path, f)
160
+ total_size += os.stat(file_path).st_size
161
+ return total_size
@@ -0,0 +1,3 @@
1
+ from truefoundry.ml.log_types.image.image import Image
2
+
3
+ __all__ = ["Image"]
@@ -0,0 +1,8 @@
1
+ import re
2
+
3
+ IMAGE_METADATA_FILE_NAME = "image_metadata.json"
4
+ DEFAULT_IMAGE_FORMAT = "png"
5
+ MISSING_PILLOW_PACKAGE_MESSAGE = (
6
+ "We need PIL package to save image.\nTo install, run `pip install pillow`"
7
+ )
8
+ IMAGE_KEY_REGEX = re.compile(r"^[a-zA-Z0-9-_]+$")
@@ -0,0 +1,357 @@
1
+ import json
2
+ import os
3
+ import posixpath
4
+ import shutil
5
+ import tempfile
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
8
+
9
+ from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
10
+ ArtifactType,
11
+ )
12
+ from truefoundry.ml.exceptions import MlFoundryException
13
+ from truefoundry.ml.log_types.artifacts.artifact import (
14
+ ArtifactVersionInternalMetadata,
15
+ _log_artifact_version_helper,
16
+ )
17
+ from truefoundry.ml.log_types.artifacts.constants import (
18
+ FILES_DIR,
19
+ INTERNAL_METADATA_PATH,
20
+ )
21
+ from truefoundry.ml.log_types.image.constants import (
22
+ DEFAULT_IMAGE_FORMAT,
23
+ IMAGE_KEY_REGEX,
24
+ IMAGE_METADATA_FILE_NAME,
25
+ MISSING_PILLOW_PACKAGE_MESSAGE,
26
+ )
27
+ from truefoundry.ml.log_types.image.image_normalizer import normalize_image
28
+ from truefoundry.ml.log_types.image.types import BoundingBoxGroups, ClassGroups
29
+ from truefoundry.ml.log_types.pydantic_base import PydanticBase
30
+ from truefoundry.ml.run_utils import get_module
31
+
32
+ if TYPE_CHECKING:
33
+ # noinspection PyUnresolvedReferences
34
+ import numpy # noqa: F401
35
+ import PIL.Image
36
+ from numpy.typing import NDArray # noqa: F401
37
+
38
+ from truefoundry.ml.mlfoundry_run import MlFoundryRun
39
+
40
+ ImageLikeNDArray = Union[
41
+ "NDArray[numpy.int8]",
42
+ "NDArray[numpy.uint8]",
43
+ "NDArray[numpy.float32]",
44
+ "NDArray[numpy.float64]",
45
+ "NDArray[numpy.bool_]",
46
+ ]
47
+ DataOrPathType = Union[str, Path, ImageLikeNDArray, "PIL.Image.Image"]
48
+ ClassGroupsType = Dict[str, Union[str, List[str]]]
49
+ BBoxGrouptype = Dict[str, List[Dict[str, Any]]]
50
+
51
+
52
+ def validate_key_name(key: str):
53
+ if not key or not IMAGE_KEY_REGEX.match(key):
54
+ raise MlFoundryException(
55
+ f"Invalid run image key: {key} should only contain alphanumeric, hyphen or underscore"
56
+ )
57
+
58
+
59
+ class ImageVersionInternalMetadata(ArtifactVersionInternalMetadata):
60
+ image_file: str
61
+ image_metadata_file: str
62
+
63
+
64
+ class Image:
65
+ def __init__(
66
+ self,
67
+ data_or_path: DataOrPathType,
68
+ caption: Optional[str] = None,
69
+ class_groups: Optional[ClassGroupsType] = None,
70
+ bbox_groups: Optional[BBoxGrouptype] = None,
71
+ ):
72
+ """Represent and log image using this class in `mlfoundry`.
73
+
74
+ You can initialize `truefoundry.ml.Image` by either by using a local path
75
+ or you can use a numpy array / PIL.Image object.
76
+
77
+ If you are using numpy array, we only support the following data types,
78
+ - bool
79
+ - integer [0 - 255]
80
+ - unsigned integer [0 - 255]
81
+ - float [0.0 - 1.0]
82
+
83
+ Any out of range value will be clipped.
84
+
85
+ As for array shape/dim, we follow the following structures,
86
+ - H x W (Grayscale)
87
+ - H x W x 1 (Grayscale)
88
+ - H x W x 3 (an RGB channel order is assumed)
89
+ - H x W x 4 (an RGBA channel order is assumed)
90
+
91
+ `PIL` package is required to log images. To install the `PIL` package,
92
+ run `pip install pillow`.
93
+
94
+ We can also log class names and bounding boxes associated with the image.
95
+ Class names and bounding boxes should be always grouped under `actuals` or
96
+ `predictions`. For example, if we have an image where the ground truth class
97
+ is "cat" and predicted class is "dog", we can represent it like,
98
+
99
+ ```python
100
+ from truefoundry.ml import Image
101
+
102
+ Image(
103
+ data_or_path=imarray,
104
+ class_groups={"actuals": "dog", "predictions": "cat"}
105
+ )
106
+ ```
107
+
108
+ You can define a bounding box using the following dictionary structure,
109
+ ```python
110
+ {
111
+ "position": {"min_x": 15, "min_y": 5, "max_x": 20, "max_y": 30}, # required, defines the position of the bounding box
112
+ # (min_x, min_y) defines the top left and
113
+ # (max_x, max_y) defines the bottom right corner of the box.
114
+ "class_name": "dog", # required, the class name of the bounding box
115
+ "caption": "dog", # optional, the caption of the bounding box.
116
+ # If not passed, the class name is set as caption.
117
+ }
118
+ ```
119
+
120
+ Args:
121
+ data_or_path (Union[str, Path, "numpy.ndarray", "PIL.Image.Image"]):
122
+ Either the local path or the image object (Numpy array or PIL Image).
123
+ caption (Optional[str], optional): A string caption or label for the image.
124
+ class_groups (Optional[Dict[str, Union[str, List[str]]]], optional):
125
+ Class names associated with the image. Expects class name(s) grouped by
126
+ `predictions` or `actuals`.
127
+ bbox_groups (Optional[Dict[str, List[Dict]]], optional): Bounding boxes
128
+ associated with the image. Expects bounding boxes grouped by `predictions`
129
+ or `actuals`.
130
+
131
+ Examples:
132
+ ### Logging images with caption and class names
133
+
134
+ ```python
135
+ from truefoundry.ml import get_client, Image
136
+ import numpy as np
137
+
138
+ client = get_client()
139
+ run = client.create_run(
140
+ ml_repo="my-classification-project",
141
+ )
142
+
143
+ imarray = np.random.randint(low=0, high=256, size=(100, 100, 3))
144
+
145
+ images_to_log = {
146
+ "logged-image-array": Image(
147
+ data_or_path=imarray,
148
+ caption="testing image logging",
149
+ class_groups={"actuals": "dog", "predictions": "cat"},
150
+ ),
151
+ }
152
+ run.log_images(images_to_log, step=1)
153
+
154
+ run.end()
155
+ ```
156
+
157
+ ### Logging images for a multi-label classification problem
158
+
159
+ ```python
160
+ from truefoundry.ml import Image
161
+
162
+ images_to_log = {
163
+ "logged-image-array": Image(
164
+ data_or_path=imarray,
165
+ caption="testing image logging",
166
+ class_groups={"actuals": ["dog", "human"], "predictions": ["cat", "human"]},
167
+ ),
168
+ }
169
+
170
+ run.log_images(images_to_log, step=1)
171
+ ```
172
+
173
+ ### Logging images with bounding boxes
174
+
175
+ ```python
176
+ from truefoundry.ml import Image
177
+
178
+ images_to_log = {
179
+ "logged-image-array": Image(
180
+ data_or_path=imarray,
181
+ caption="testing image logging",
182
+ bbox_groups={
183
+ "predictions": [
184
+ {
185
+ "position": {"min_x": 5, "min_y": 5, "max_x": 20, "max_y": 30},
186
+ "class_name": "cat",
187
+ }
188
+ ],
189
+ "actuals": [
190
+ {
191
+ "position": {"min_x": 15, "min_y": 5, "max_x": 20, "max_y": 30},
192
+ "class_name": "dog",
193
+ "caption": "dog",
194
+ }
195
+ ],
196
+ },
197
+ ),
198
+ }
199
+
200
+ run.log_images(images_to_log, step=1)
201
+ ```
202
+ """
203
+
204
+ self._caption: Optional[str] = None
205
+ self._class_groups: Optional[ClassGroups] = None
206
+ self._bbox_groups: Optional[BoundingBoxGroups] = None
207
+
208
+ self._image: Optional["PIL.Image.Image"] = None
209
+ self._image_artifact_path = None
210
+ self._local_image_path: Optional[str] = None
211
+
212
+ self._init_image(data_or_path)
213
+ self._init_metadata(
214
+ caption=caption, class_groups=class_groups, bbox_groups=bbox_groups
215
+ )
216
+
217
+ @property
218
+ def image(self) -> "PIL.Image.Image":
219
+ if self._image is None:
220
+ raise MlFoundryException("Image is not initialized")
221
+ return self._image
222
+
223
+ @property
224
+ def caption(self) -> Optional[str]:
225
+ return self._caption
226
+
227
+ @property
228
+ def class_groups(self) -> Optional[ClassGroups]:
229
+ return self._class_groups
230
+
231
+ @property
232
+ def bbox_groups(self) -> Optional[BoundingBoxGroups]:
233
+ return self._bbox_groups
234
+
235
+ def save_image_to_local_dir(self, local_dir) -> str:
236
+ if self._local_image_path is None:
237
+ image_file_name = self._serialize_and_save_image_as_artifact(local_dir)
238
+ else:
239
+ image_file_name = self._copy_local_image_as_artifact(local_dir)
240
+
241
+ return image_file_name
242
+
243
+ def _serialize_and_save_image_as_artifact(self, local_dir: str) -> str:
244
+ file_name = f"image.{DEFAULT_IMAGE_FORMAT}"
245
+ local_path = os.path.join(local_dir, file_name)
246
+ self.image.save(local_path)
247
+
248
+ return file_name
249
+
250
+ def _copy_local_image_as_artifact(self, local_dir: str) -> str:
251
+ assert self._local_image_path is not None
252
+ file_name = os.path.basename(self._local_image_path)
253
+ new_local_image_path = os.path.join(local_dir, file_name)
254
+ shutil.copy2(self._local_image_path, new_local_image_path)
255
+ return file_name
256
+
257
+ def _init_image(self, data_or_path: DataOrPathType):
258
+ pil_image_module = get_module(
259
+ module_name="PIL.Image",
260
+ required=True,
261
+ error_message=MISSING_PILLOW_PACKAGE_MESSAGE,
262
+ )
263
+ if isinstance(data_or_path, (str, Path)):
264
+ self._local_image_path = os.path.abspath(data_or_path)
265
+ with pil_image_module.open(data_or_path) as image:
266
+ image.load()
267
+ self._image = image
268
+ else:
269
+ self._image = normalize_image(data_or_path)
270
+
271
+ def _init_metadata(
272
+ self,
273
+ caption: Optional[str],
274
+ class_groups: Optional[ClassGroupsType],
275
+ bbox_groups: Optional[Dict[str, Any]],
276
+ ):
277
+ if caption is not None:
278
+ self._caption = str(caption)
279
+
280
+ if class_groups:
281
+ self._class_groups = ClassGroups.parse_obj(class_groups)
282
+
283
+ if bbox_groups:
284
+ self._bbox_groups = BoundingBoxGroups.parse_obj(bbox_groups)
285
+
286
+ def _to_dict(self) -> Dict[str, Any]:
287
+ dict_ = {}
288
+ dict_["caption"] = self._caption
289
+ dict_["class_groups"] = (
290
+ self.class_groups.to_dict() if self.class_groups is not None else None
291
+ )
292
+ dict_["bbox_groups"] = (
293
+ self.bbox_groups.to_dict() if self.bbox_groups is not None else None
294
+ )
295
+
296
+ return dict_
297
+
298
+ def save(
299
+ self,
300
+ run: "MlFoundryRun",
301
+ key: str,
302
+ step: int = 0,
303
+ ):
304
+ validate_key_name(key)
305
+
306
+ # creating temp dir, files directory, .truefoundry directory
307
+ temp_dir = tempfile.TemporaryDirectory(prefix="truefoundry-")
308
+
309
+ local_internal_metadata_path = os.path.join(
310
+ temp_dir.name, INTERNAL_METADATA_PATH
311
+ )
312
+ os.makedirs(os.path.dirname(local_internal_metadata_path), exist_ok=True)
313
+
314
+ local_files_dir = os.path.join(temp_dir.name, FILES_DIR)
315
+ os.makedirs(local_files_dir, exist_ok=True)
316
+
317
+ try:
318
+ # saving the image file
319
+ image_file_name = self.save_image_to_local_dir(local_files_dir)
320
+
321
+ # saving the image_metadata
322
+ local_image_metadata_file_path = os.path.join(
323
+ local_files_dir, IMAGE_METADATA_FILE_NAME
324
+ )
325
+ with open(local_image_metadata_file_path, "w") as fp:
326
+ fp.write(ImageRunLogType(value=self._to_dict()).json())
327
+
328
+ # creating and saving the internal_metadata file
329
+ internal_metadata = ImageVersionInternalMetadata(
330
+ files_dir=FILES_DIR,
331
+ # joining with posixpath for avoiding backslash in case logged from windows machine
332
+ image_file=posixpath.join(FILES_DIR, image_file_name),
333
+ image_metadata_file=posixpath.join(FILES_DIR, IMAGE_METADATA_FILE_NAME),
334
+ )
335
+ with open(local_internal_metadata_path, "w") as f:
336
+ json.dump(internal_metadata.dict(), f)
337
+
338
+ except Exception as e:
339
+ temp_dir.cleanup()
340
+ raise MlFoundryException("Failed to log Image") from e
341
+
342
+ _log_artifact_version_helper(
343
+ run=run,
344
+ name=key,
345
+ artifact_type=ArtifactType.IMAGE,
346
+ artifact_dir=temp_dir,
347
+ internal_metadata=internal_metadata,
348
+ step=step,
349
+ )
350
+
351
+
352
+ class ImageRunLogType(PydanticBase):
353
+ value: Dict[str, Any]
354
+
355
+ @staticmethod
356
+ def get_log_type() -> str:
357
+ return "image"
@@ -0,0 +1,102 @@
1
+ # The logic is copied from
2
+ # https://github.com/truefoundry/mlfoundry-server/blob/c2334ace8f35322cd5e50c275b8df08327688c01/mlflow/tracking/client.py#L1188
3
+ from typing import TYPE_CHECKING, Union
4
+
5
+ import numpy as np
6
+
7
+ from truefoundry.ml.exceptions import MlFoundryException
8
+ from truefoundry.ml.log_types.image.constants import MISSING_PILLOW_PACKAGE_MESSAGE
9
+ from truefoundry.ml.logger import logger
10
+ from truefoundry.ml.run_utils import get_module
11
+
12
+ if TYPE_CHECKING:
13
+ import numpy # noqa: F401
14
+ import PIL.Image
15
+ from numpy.typing import NDArray # noqa: F401
16
+
17
+ ImageLikeNDArray = Union[
18
+ "NDArray[numpy.int8]",
19
+ "NDArray[numpy.uint8]",
20
+ "NDArray[numpy.float32]",
21
+ "NDArray[numpy.float64]",
22
+ "NDArray[numpy.bool_]",
23
+ ]
24
+
25
+
26
+ def _normalize_to_uint8(x):
27
+ # Based on: https://github.com/matplotlib/matplotlib/blob/06567e021f21be046b6d6dcf00380c1cb9adaf3c/lib/matplotlib/image.py#L684
28
+
29
+ is_int = np.issubdtype(x.dtype, np.integer)
30
+ low = 0
31
+ high = 255 if is_int else 1
32
+ if x.min() < low or x.max() > high:
33
+ msg = (
34
+ "Out-of-range values are detected. "
35
+ "Clipping array (dtype: '{}') to [{}, {}]".format(x.dtype, low, high)
36
+ )
37
+ logger.warning(msg)
38
+ x = np.clip(x, low, high)
39
+
40
+ # float or bool
41
+ if not is_int:
42
+ x = x * 255
43
+
44
+ return x.astype(np.uint8)
45
+
46
+
47
+ def _convert_numpy_to_pil_image(image: ImageLikeNDArray) -> "PIL.Image.Image":
48
+ pil_image_module = get_module(
49
+ module_name="PIL.Image",
50
+ required=True,
51
+ error_message=MISSING_PILLOW_PACKAGE_MESSAGE,
52
+ )
53
+ valid_data_types = {
54
+ "b": "bool",
55
+ "i": "signed integer",
56
+ "u": "unsigned integer",
57
+ "f": "floating",
58
+ }
59
+
60
+ if image.dtype.kind not in valid_data_types.keys():
61
+ raise TypeError(
62
+ "Invalid array data type: '{}'. Must be one of {}".format(
63
+ image.dtype, list(valid_data_types.values())
64
+ )
65
+ )
66
+
67
+ if image.ndim not in [2, 3]:
68
+ raise ValueError(
69
+ "`image` must be a 2D or 3D array but got a {}D array".format(image.ndim)
70
+ )
71
+
72
+ if (image.ndim == 3) and (image.shape[2] not in [1, 3, 4]):
73
+ raise ValueError(
74
+ "Invalid channel length: {}. Must be one of [1, 3, 4]".format(
75
+ image.shape[2]
76
+ )
77
+ )
78
+
79
+ # squeeze a 3D grayscale image since `Image.fromarray` doesn't accept it.
80
+ if image.ndim == 3 and image.shape[2] == 1:
81
+ image = image[:, :, 0]
82
+
83
+ image = _normalize_to_uint8(image)
84
+
85
+ return pil_image_module.fromarray(image)
86
+
87
+
88
+ def normalize_image(
89
+ image: Union["PIL.Image.Image", ImageLikeNDArray],
90
+ ) -> "PIL.Image.Image":
91
+ pil_image_module = get_module(
92
+ module_name="PIL.Image",
93
+ required=True,
94
+ error_message=MISSING_PILLOW_PACKAGE_MESSAGE,
95
+ )
96
+ if isinstance(image, pil_image_module.Image):
97
+ return image
98
+ if isinstance(image, np.ndarray):
99
+ return _convert_numpy_to_pil_image(image)
100
+ raise MlFoundryException(
101
+ f"image should be of type PIL Image/np.ndarray got type {type(image)}"
102
+ )
@@ -0,0 +1,68 @@
1
+ import enum
2
+ from typing import Dict, List, Optional
3
+
4
+ from truefoundry.pydantic_v1 import BaseModel, NonEmptyStr, root_validator
5
+
6
+
7
+ class Group(enum.Enum):
8
+ ACTUALS = "actuals"
9
+ PREDICTIONS = "predictions"
10
+
11
+
12
+ class Position(BaseModel):
13
+ min_x: float
14
+ min_y: float
15
+ max_x: float
16
+ max_y: float
17
+
18
+ class Config:
19
+ allow_mutation = False
20
+
21
+
22
+ class BoundingBox(BaseModel):
23
+ position: Position
24
+ class_name: NonEmptyStr
25
+ caption: Optional[str] = None
26
+
27
+ class Config:
28
+ allow_mutation = False
29
+
30
+ @root_validator(pre=True)
31
+ def set_caption_if_not_passed(cls, values):
32
+ if not values.get("caption"):
33
+ values["caption"] = values.get("class_name")
34
+
35
+ return values
36
+
37
+
38
+ class BoundingBoxGroups(BaseModel):
39
+ __root__: Dict[Group, List[BoundingBox]]
40
+
41
+ class Config:
42
+ allow_mutation = False
43
+ use_enum_values = True
44
+
45
+ def to_dict(self):
46
+ return self.dict()["__root__"]
47
+
48
+
49
+ class ClassGroups(BaseModel):
50
+ __root__: Dict[Group, List[NonEmptyStr]]
51
+
52
+ class Config:
53
+ allow_mutation = False
54
+ use_enum_values = True
55
+
56
+ @root_validator(pre=True)
57
+ def transform_class_to_classes(cls, values):
58
+ values = values["__root__"]
59
+ processed_values = {}
60
+ for group, classes in values.items():
61
+ if isinstance(classes, str):
62
+ processed_values[group] = [classes]
63
+ else:
64
+ processed_values[group] = classes
65
+ return {"__root__": processed_values}
66
+
67
+ def to_dict(self):
68
+ return self.dict()["__root__"]