truefoundry 0.3.3__py3-none-any.whl → 0.4.0.dev0__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 (224) hide show
  1. truefoundry/cli/__main__.py +3 -17
  2. truefoundry/common/__init__.py +0 -0
  3. truefoundry/common/request_utils.py +56 -0
  4. truefoundry/deploy/cli/cli.py +1 -1
  5. truefoundry/deploy/lib/auth/credential_provider.py +2 -12
  6. truefoundry/deploy/lib/clients/servicefoundry_client.py +0 -9
  7. truefoundry/deploy/lib/exceptions.py +1 -6
  8. truefoundry/deploy/lib/session.py +1 -16
  9. truefoundry/langchain/truefoundry_chat.py +1 -1
  10. truefoundry/langchain/truefoundry_embeddings.py +1 -1
  11. truefoundry/langchain/truefoundry_llm.py +1 -1
  12. truefoundry/langchain/utils.py +0 -41
  13. truefoundry/ml/__init__.py +46 -6
  14. truefoundry/ml/artifact/__init__.py +0 -0
  15. truefoundry/ml/artifact/truefoundry_artifact_repo.py +1120 -0
  16. truefoundry/ml/autogen/__init__.py +0 -0
  17. truefoundry/ml/autogen/client/__init__.py +373 -0
  18. truefoundry/ml/autogen/client/api/__init__.py +16 -0
  19. truefoundry/ml/autogen/client/api/auth_api.py +184 -0
  20. truefoundry/ml/autogen/client/api/deprecated_api.py +605 -0
  21. truefoundry/ml/autogen/client/api/experiments_api.py +2109 -0
  22. truefoundry/ml/autogen/client/api/health_api.py +299 -0
  23. truefoundry/ml/autogen/client/api/metrics_api.py +371 -0
  24. truefoundry/ml/autogen/client/api/mlfoundry_artifacts_api.py +7213 -0
  25. truefoundry/ml/autogen/client/api/python_deployment_config_api.py +201 -0
  26. truefoundry/ml/autogen/client/api/run_artifacts_api.py +231 -0
  27. truefoundry/ml/autogen/client/api/runs_api.py +2919 -0
  28. truefoundry/ml/autogen/client/api_client.py +822 -0
  29. truefoundry/ml/autogen/client/api_response.py +30 -0
  30. truefoundry/ml/autogen/client/configuration.py +489 -0
  31. truefoundry/ml/autogen/client/exceptions.py +161 -0
  32. truefoundry/ml/autogen/client/models/__init__.py +344 -0
  33. truefoundry/ml/autogen/client/models/add_custom_metrics_to_model_version_request_dto.py +69 -0
  34. truefoundry/ml/autogen/client/models/add_features_to_model_version_request_dto.py +83 -0
  35. truefoundry/ml/autogen/client/models/agent.py +125 -0
  36. truefoundry/ml/autogen/client/models/agent_app.py +118 -0
  37. truefoundry/ml/autogen/client/models/agent_open_api_tool.py +143 -0
  38. truefoundry/ml/autogen/client/models/agent_open_api_tool_with_fqn.py +144 -0
  39. truefoundry/ml/autogen/client/models/agent_with_fqn.py +127 -0
  40. truefoundry/ml/autogen/client/models/artifact_dto.py +115 -0
  41. truefoundry/ml/autogen/client/models/artifact_response_dto.py +75 -0
  42. truefoundry/ml/autogen/client/models/artifact_type.py +39 -0
  43. truefoundry/ml/autogen/client/models/artifact_version_dto.py +141 -0
  44. truefoundry/ml/autogen/client/models/artifact_version_response_dto.py +77 -0
  45. truefoundry/ml/autogen/client/models/artifact_version_status.py +35 -0
  46. truefoundry/ml/autogen/client/models/assistant_message.py +89 -0
  47. truefoundry/ml/autogen/client/models/authorize_user_for_model_request_dto.py +69 -0
  48. truefoundry/ml/autogen/client/models/authorize_user_for_model_version_request_dto.py +69 -0
  49. truefoundry/ml/autogen/client/models/backfill_default_storage_integration_id_request_dto.py +67 -0
  50. truefoundry/ml/autogen/client/models/blob_storage_reference.py +93 -0
  51. truefoundry/ml/autogen/client/models/body_get_search_runs_get.py +72 -0
  52. truefoundry/ml/autogen/client/models/chat_prompt.py +156 -0
  53. truefoundry/ml/autogen/client/models/chat_prompt_messages_inner.py +171 -0
  54. truefoundry/ml/autogen/client/models/columns_dto.py +73 -0
  55. truefoundry/ml/autogen/client/models/content.py +153 -0
  56. truefoundry/ml/autogen/client/models/content1.py +153 -0
  57. truefoundry/ml/autogen/client/models/content2.py +174 -0
  58. truefoundry/ml/autogen/client/models/content2_any_of_inner.py +150 -0
  59. truefoundry/ml/autogen/client/models/create_artifact_request_dto.py +74 -0
  60. truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +66 -0
  61. truefoundry/ml/autogen/client/models/create_artifact_version_request_dto.py +74 -0
  62. truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +66 -0
  63. truefoundry/ml/autogen/client/models/create_dataset_request_dto.py +76 -0
  64. truefoundry/ml/autogen/client/models/create_experiment_request_dto.py +94 -0
  65. truefoundry/ml/autogen/client/models/create_experiment_response_dto.py +67 -0
  66. truefoundry/ml/autogen/client/models/create_model_version_request_dto.py +95 -0
  67. truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_request_dto.py +73 -0
  68. truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_response_dto.py +79 -0
  69. truefoundry/ml/autogen/client/models/create_multi_part_upload_request_dto.py +73 -0
  70. truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +72 -0
  71. truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +68 -0
  72. truefoundry/ml/autogen/client/models/create_run_request_dto.py +97 -0
  73. truefoundry/ml/autogen/client/models/create_run_response_dto.py +76 -0
  74. truefoundry/ml/autogen/client/models/dataset_dto.py +112 -0
  75. truefoundry/ml/autogen/client/models/dataset_response_dto.py +75 -0
  76. truefoundry/ml/autogen/client/models/delete_artifact_versions_request_dto.py +65 -0
  77. truefoundry/ml/autogen/client/models/delete_dataset_request_dto.py +74 -0
  78. truefoundry/ml/autogen/client/models/delete_model_version_request_dto.py +65 -0
  79. truefoundry/ml/autogen/client/models/delete_run_request.py +65 -0
  80. truefoundry/ml/autogen/client/models/delete_tag_request_dto.py +68 -0
  81. truefoundry/ml/autogen/client/models/experiment_dto.py +127 -0
  82. truefoundry/ml/autogen/client/models/experiment_id_request_dto.py +67 -0
  83. truefoundry/ml/autogen/client/models/experiment_response_dto.py +75 -0
  84. truefoundry/ml/autogen/client/models/experiment_tag_dto.py +69 -0
  85. truefoundry/ml/autogen/client/models/feature_dto.py +68 -0
  86. truefoundry/ml/autogen/client/models/feature_value_type.py +35 -0
  87. truefoundry/ml/autogen/client/models/file_info_dto.py +76 -0
  88. truefoundry/ml/autogen/client/models/finalize_artifact_version_request_dto.py +101 -0
  89. truefoundry/ml/autogen/client/models/get_experiment_response_dto.py +88 -0
  90. truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +76 -0
  91. truefoundry/ml/autogen/client/models/get_metric_history_response.py +79 -0
  92. truefoundry/ml/autogen/client/models/get_signed_url_for_dataset_write_request_dto.py +68 -0
  93. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_request_dto.py +68 -0
  94. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_response_dto.py +81 -0
  95. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_request_dto.py +69 -0
  96. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_response_dto.py +83 -0
  97. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_request_dto.py +68 -0
  98. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_response_dto.py +81 -0
  99. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_write_response_dto.py +81 -0
  100. truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +74 -0
  101. truefoundry/ml/autogen/client/models/http_validation_error.py +82 -0
  102. truefoundry/ml/autogen/client/models/image_content_part.py +87 -0
  103. truefoundry/ml/autogen/client/models/image_url.py +75 -0
  104. truefoundry/ml/autogen/client/models/internal_metadata.py +180 -0
  105. truefoundry/ml/autogen/client/models/latest_run_log_dto.py +78 -0
  106. truefoundry/ml/autogen/client/models/list_artifact_versions_request_dto.py +107 -0
  107. truefoundry/ml/autogen/client/models/list_artifact_versions_response_dto.py +87 -0
  108. truefoundry/ml/autogen/client/models/list_artifacts_request_dto.py +96 -0
  109. truefoundry/ml/autogen/client/models/list_artifacts_response_dto.py +86 -0
  110. truefoundry/ml/autogen/client/models/list_colums_response_dto.py +75 -0
  111. truefoundry/ml/autogen/client/models/list_datasets_request_dto.py +78 -0
  112. truefoundry/ml/autogen/client/models/list_datasets_response_dto.py +86 -0
  113. truefoundry/ml/autogen/client/models/list_experiments_response_dto.py +86 -0
  114. truefoundry/ml/autogen/client/models/list_files_for_artifact_version_request_dto.py +76 -0
  115. truefoundry/ml/autogen/client/models/list_files_for_artifact_versions_response_dto.py +82 -0
  116. truefoundry/ml/autogen/client/models/list_files_for_dataset_request_dto.py +76 -0
  117. truefoundry/ml/autogen/client/models/list_files_for_dataset_response_dto.py +82 -0
  118. truefoundry/ml/autogen/client/models/list_latest_run_logs_response_dto.py +82 -0
  119. truefoundry/ml/autogen/client/models/list_metric_history_request_dto.py +69 -0
  120. truefoundry/ml/autogen/client/models/list_metric_history_response_dto.py +84 -0
  121. truefoundry/ml/autogen/client/models/list_model_version_response_dto.py +87 -0
  122. truefoundry/ml/autogen/client/models/list_model_versions_request_dto.py +93 -0
  123. truefoundry/ml/autogen/client/models/list_models_request_dto.py +89 -0
  124. truefoundry/ml/autogen/client/models/list_models_response_dto.py +84 -0
  125. truefoundry/ml/autogen/client/models/list_run_artifacts_response_dto.py +84 -0
  126. truefoundry/ml/autogen/client/models/list_run_logs_response_dto.py +82 -0
  127. truefoundry/ml/autogen/client/models/list_seed_experiments_response_dto.py +81 -0
  128. truefoundry/ml/autogen/client/models/log_batch_request_dto.py +106 -0
  129. truefoundry/ml/autogen/client/models/log_metric_request_dto.py +80 -0
  130. truefoundry/ml/autogen/client/models/log_param_request_dto.py +76 -0
  131. truefoundry/ml/autogen/client/models/method.py +37 -0
  132. truefoundry/ml/autogen/client/models/metric_collection_dto.py +82 -0
  133. truefoundry/ml/autogen/client/models/metric_dto.py +76 -0
  134. truefoundry/ml/autogen/client/models/mime_type.py +37 -0
  135. truefoundry/ml/autogen/client/models/model_configuration.py +103 -0
  136. truefoundry/ml/autogen/client/models/model_dto.py +122 -0
  137. truefoundry/ml/autogen/client/models/model_response_dto.py +75 -0
  138. truefoundry/ml/autogen/client/models/model_schema_dto.py +85 -0
  139. truefoundry/ml/autogen/client/models/model_version_dto.py +163 -0
  140. truefoundry/ml/autogen/client/models/model_version_response_dto.py +75 -0
  141. truefoundry/ml/autogen/client/models/multi_part_upload_dto.py +107 -0
  142. truefoundry/ml/autogen/client/models/multi_part_upload_response_dto.py +79 -0
  143. truefoundry/ml/autogen/client/models/multi_part_upload_storage_provider.py +34 -0
  144. truefoundry/ml/autogen/client/models/notify_artifact_version_failure_dto.py +65 -0
  145. truefoundry/ml/autogen/client/models/openapi_spec.py +152 -0
  146. truefoundry/ml/autogen/client/models/param_dto.py +66 -0
  147. truefoundry/ml/autogen/client/models/parameters.py +84 -0
  148. truefoundry/ml/autogen/client/models/prediction_type.py +34 -0
  149. truefoundry/ml/autogen/client/models/resolve_agent_app_response_dto.py +75 -0
  150. truefoundry/ml/autogen/client/models/restore_run_request_dto.py +65 -0
  151. truefoundry/ml/autogen/client/models/run_data_dto.py +104 -0
  152. truefoundry/ml/autogen/client/models/run_dto.py +84 -0
  153. truefoundry/ml/autogen/client/models/run_info_dto.py +105 -0
  154. truefoundry/ml/autogen/client/models/run_log_dto.py +90 -0
  155. truefoundry/ml/autogen/client/models/run_log_input_dto.py +80 -0
  156. truefoundry/ml/autogen/client/models/run_response_dto.py +75 -0
  157. truefoundry/ml/autogen/client/models/run_tag_dto.py +66 -0
  158. truefoundry/ml/autogen/client/models/search_runs_request_dto.py +94 -0
  159. truefoundry/ml/autogen/client/models/search_runs_response_dto.py +84 -0
  160. truefoundry/ml/autogen/client/models/set_experiment_tag_request_dto.py +73 -0
  161. truefoundry/ml/autogen/client/models/set_tag_request_dto.py +76 -0
  162. truefoundry/ml/autogen/client/models/signed_url_dto.py +69 -0
  163. truefoundry/ml/autogen/client/models/stop.py +152 -0
  164. truefoundry/ml/autogen/client/models/store_run_logs_request_dto.py +83 -0
  165. truefoundry/ml/autogen/client/models/system_message.py +89 -0
  166. truefoundry/ml/autogen/client/models/text.py +153 -0
  167. truefoundry/ml/autogen/client/models/text_content_part.py +84 -0
  168. truefoundry/ml/autogen/client/models/update_artifact_version_request_dto.py +74 -0
  169. truefoundry/ml/autogen/client/models/update_dataset_request_dto.py +74 -0
  170. truefoundry/ml/autogen/client/models/update_experiment_request_dto.py +74 -0
  171. truefoundry/ml/autogen/client/models/update_model_version_request_dto.py +93 -0
  172. truefoundry/ml/autogen/client/models/update_run_request_dto.py +78 -0
  173. truefoundry/ml/autogen/client/models/update_run_response_dto.py +76 -0
  174. truefoundry/ml/autogen/client/models/url.py +153 -0
  175. truefoundry/ml/autogen/client/models/user_message.py +89 -0
  176. truefoundry/ml/autogen/client/models/validation_error.py +87 -0
  177. truefoundry/ml/autogen/client/models/validation_error_loc_inner.py +154 -0
  178. truefoundry/ml/autogen/client/rest.py +426 -0
  179. truefoundry/ml/autogen/client_README.md +322 -0
  180. truefoundry/ml/cli/__init__.py +0 -0
  181. truefoundry/ml/cli/cli.py +18 -0
  182. truefoundry/ml/cli/commands/__init__.py +3 -0
  183. truefoundry/ml/cli/commands/download.py +87 -0
  184. truefoundry/ml/constants.py +84 -0
  185. truefoundry/ml/enums.py +70 -0
  186. truefoundry/ml/env_vars.py +13 -0
  187. truefoundry/ml/exceptions.py +8 -0
  188. truefoundry/ml/git_info.py +60 -0
  189. truefoundry/ml/internal_namespace.py +52 -0
  190. truefoundry/ml/log_types/__init__.py +4 -0
  191. truefoundry/ml/log_types/artifacts/artifact.py +427 -0
  192. truefoundry/ml/log_types/artifacts/constants.py +33 -0
  193. truefoundry/ml/log_types/artifacts/dataset.py +383 -0
  194. truefoundry/ml/log_types/artifacts/general_artifact.py +110 -0
  195. truefoundry/ml/log_types/artifacts/model.py +628 -0
  196. truefoundry/ml/log_types/artifacts/model_extras.py +48 -0
  197. truefoundry/ml/log_types/artifacts/utils.py +161 -0
  198. truefoundry/ml/log_types/image/__init__.py +3 -0
  199. truefoundry/ml/log_types/image/constants.py +8 -0
  200. truefoundry/ml/log_types/image/image.py +358 -0
  201. truefoundry/ml/log_types/image/image_normalizer.py +101 -0
  202. truefoundry/ml/log_types/image/types.py +68 -0
  203. truefoundry/ml/log_types/plot.py +281 -0
  204. truefoundry/ml/log_types/pydantic_base.py +10 -0
  205. truefoundry/ml/log_types/utils.py +12 -0
  206. truefoundry/ml/logger.py +17 -0
  207. truefoundry/ml/login.py +241 -0
  208. truefoundry/ml/mlfoundry_api.py +1620 -0
  209. truefoundry/ml/mlfoundry_run.py +1238 -0
  210. truefoundry/ml/run_utils.py +102 -0
  211. truefoundry/ml/services/__init__.py +0 -0
  212. truefoundry/ml/services/auth_service.py +109 -0
  213. truefoundry/ml/services/entities.py +108 -0
  214. truefoundry/ml/services/servicefoundry_service.py +35 -0
  215. truefoundry/ml/services/utils.py +122 -0
  216. truefoundry/ml/session.py +271 -0
  217. truefoundry/ml/validation_utils.py +346 -0
  218. truefoundry/pydantic_v1.py +5 -1
  219. {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev0.dist-info}/METADATA +19 -12
  220. truefoundry-0.4.0.dev0.dist-info/RECORD +342 -0
  221. truefoundry-0.3.3.dist-info/RECORD +0 -136
  222. /truefoundry/{python_deploy_codegen.py → deploy/python_deploy_codegen.py} +0 -0
  223. {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev0.dist-info}/WHEEL +0 -0
  224. {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,281 @@
1
+ import enum
2
+ import json
3
+ import os
4
+ import posixpath
5
+ import sys
6
+ import tempfile
7
+ from typing import TYPE_CHECKING, 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.pydantic_base import PydanticBase
22
+ from truefoundry.ml.log_types.utils import validate_key_name
23
+ from truefoundry.pydantic_v1 import BaseModel
24
+
25
+ if TYPE_CHECKING:
26
+ import matplotlib.figure
27
+ import matplotlib.pyplot
28
+ import plotly.graph_objects
29
+
30
+ from truefoundry.ml import MlFoundryRun
31
+
32
+ PlotObjType = Union[
33
+ "matplotlib.figure.Figure",
34
+ "plotly.graph_objects.Figure",
35
+ "matplotlib.pyplot",
36
+ ]
37
+
38
+
39
+ @enum.unique
40
+ class Format(enum.Enum):
41
+ SVG = "SVG"
42
+ HTML = "HTML"
43
+ PNG = "PNG"
44
+
45
+
46
+ def _is_matplotlib_figure(fig) -> bool:
47
+ if "matplotlib" not in sys.modules:
48
+ return False
49
+ import matplotlib
50
+
51
+ return isinstance(fig, matplotlib.figure.Figure)
52
+
53
+
54
+ def _is_matplotlib_plt(plt) -> bool:
55
+ if "matplotlib" not in sys.modules:
56
+ return False
57
+ return getattr(plt, "__name__", "") == "matplotlib.pyplot"
58
+
59
+
60
+ def _is_plotly_figure(fig) -> bool:
61
+ if "plotly" not in sys.modules:
62
+ return False
63
+
64
+ import plotly
65
+
66
+ return isinstance(fig, plotly.graph_objects.Figure)
67
+
68
+
69
+ def get_plot_file_name(format: Format) -> str:
70
+ return f"plot.{format.value.lower()}"
71
+
72
+
73
+ class PlotArtifact(BaseModel):
74
+ artifact_file: str
75
+ format: Format
76
+
77
+ class Config:
78
+ allow_mutation = False
79
+ use_enum_values = True
80
+
81
+
82
+ def _save_matplotlib_figure(
83
+ figure: "matplotlib.figure.Figure",
84
+ key: str,
85
+ step: int,
86
+ local_dir: str,
87
+ ) -> PlotArtifact:
88
+ supported_formats = figure.canvas.get_supported_filetypes().keys()
89
+ if "svg" in supported_formats:
90
+ format_ = Format.SVG
91
+ elif "png" in supported_formats:
92
+ format_ = Format.PNG
93
+ else:
94
+ raise MlFoundryException(
95
+ f"Could not save {key} {figure} matplotlib figure"
96
+ "in either SVG or PNG format"
97
+ )
98
+ file_path = get_plot_file_name(format=format_)
99
+ local_path = os.path.join(local_dir, file_path)
100
+ figure.savefig(local_path)
101
+ return PlotArtifact(artifact_file=file_path, format=format_)
102
+
103
+
104
+ def _save_matplotlib_plt(
105
+ plt: "matplotlib.pyplot",
106
+ key: str,
107
+ step: int,
108
+ local_dir: str,
109
+ ) -> PlotArtifact:
110
+ figure = plt.gcf()
111
+ return _save_matplotlib_figure(
112
+ figure=figure, key=key, step=step, local_dir=local_dir
113
+ )
114
+
115
+
116
+ def _save_plotly_figure(
117
+ figure: "plotly.graph_objects.Figure",
118
+ key: str,
119
+ step: int,
120
+ local_dir: str,
121
+ ) -> PlotArtifact:
122
+ format_ = Format.HTML
123
+ file_path = get_plot_file_name(format=format_)
124
+ local_path = os.path.join(local_dir, file_path)
125
+ figure.write_html(local_path, include_plotlyjs="cdn", auto_open=False)
126
+ return PlotArtifact(artifact_file=file_path, format=format_)
127
+
128
+
129
+ class Plot:
130
+ def __init__(self, plot_obj: PlotObjType):
131
+ self._plot_obj = plot_obj
132
+
133
+ def _save_plot(self, key: str, step: int, local_dir: str) -> PlotArtifact:
134
+ if _is_matplotlib_plt(self._plot_obj):
135
+ return _save_matplotlib_plt(
136
+ plt=self._plot_obj, key=key, step=step, local_dir=local_dir
137
+ )
138
+
139
+ if _is_matplotlib_figure(self._plot_obj):
140
+ return _save_matplotlib_figure(
141
+ figure=self._plot_obj, key=key, step=step, local_dir=local_dir
142
+ )
143
+
144
+ if _is_plotly_figure(self._plot_obj):
145
+ return _save_plotly_figure(
146
+ figure=self._plot_obj, key=key, step=step, local_dir=local_dir
147
+ )
148
+
149
+ raise MlFoundryException(
150
+ f"Unknown type: {type(self._plot_obj)}"
151
+ "Supported types are, matplotlib.figure.Figure, matplotlib.pyplot"
152
+ " and plotly.graph_objects.Figure"
153
+ )
154
+
155
+ def save(
156
+ self,
157
+ run: "MlFoundryRun",
158
+ key: str,
159
+ step: int = 0,
160
+ ):
161
+ validate_key_name(key)
162
+
163
+ # creating a temp dir which will be logged
164
+ temp_dir = tempfile.TemporaryDirectory(prefix="truefoundry-")
165
+ local_files_dir = os.path.join(temp_dir.name, FILES_DIR)
166
+ os.makedirs(local_files_dir, exist_ok=True)
167
+
168
+ # save plot locally
169
+ plot_artifact = self._save_plot(key=key, step=step, local_dir=local_files_dir)
170
+
171
+ # save internal metadata
172
+ internal_metadata = PlotVersionInternalMetadata(
173
+ files_dir=FILES_DIR,
174
+ plot_file=posixpath.join(FILES_DIR, plot_artifact.artifact_file),
175
+ )
176
+ local_internal_metadata_path = os.path.join(
177
+ temp_dir.name, INTERNAL_METADATA_PATH
178
+ )
179
+ os.makedirs(os.path.dirname(local_internal_metadata_path), exist_ok=True)
180
+ with open(local_internal_metadata_path, "w") as f:
181
+ json.dump(internal_metadata.dict(), f)
182
+
183
+ # log the artifact
184
+ _log_artifact_version_helper(
185
+ run=run,
186
+ name=key,
187
+ artifact_type=ArtifactType.PLOT,
188
+ artifact_dir=temp_dir,
189
+ internal_metadata=internal_metadata,
190
+ step=step,
191
+ )
192
+
193
+
194
+ class PlotRunLogType(PydanticBase):
195
+ value: PlotArtifact
196
+
197
+ @staticmethod
198
+ def get_log_type() -> str:
199
+ return "plot"
200
+
201
+
202
+ class PlotVersionInternalMetadata(ArtifactVersionInternalMetadata):
203
+ plot_file: str
204
+
205
+
206
+ if __name__ == "__main__":
207
+ import matplotlib.pyplot as plt
208
+ import numpy as np
209
+ import plotly.express as px
210
+
211
+ from truefoundry.ml import get_client
212
+
213
+ client = get_client()
214
+
215
+ run = client.create_run(ml_repo="plot-test1")
216
+
217
+ df = px.data.iris()
218
+ fig = px.scatter(
219
+ df,
220
+ x="sepal_width",
221
+ y="sepal_length",
222
+ color="species",
223
+ size="petal_length",
224
+ hover_data=["petal_width"],
225
+ )
226
+ Plot(fig).save(run, "foo")
227
+
228
+ df = px.data.tips()
229
+ fig = px.histogram(
230
+ df,
231
+ x="total_bill",
232
+ y="tip",
233
+ color="sex",
234
+ marginal="rug",
235
+ hover_data=df.columns,
236
+ )
237
+ Plot(fig).save(run, "foo", step=1)
238
+
239
+ names = ["group_a", "group_b", "group_c"]
240
+ values = [1, 10, 100]
241
+ plt.figure(figsize=(9, 3))
242
+ plt.subplot(131)
243
+ plt.bar(names, values)
244
+ plt.subplot(132)
245
+ plt.scatter(names, values)
246
+ plt.subplot(133)
247
+ plt.plot(names, values)
248
+ plt.suptitle("Categorical Plotting")
249
+
250
+ Plot(plt).save(run, "bar")
251
+
252
+ plt.clf()
253
+
254
+ data = {
255
+ "a": np.arange(50),
256
+ "c": np.random.randint(0, 50, 50),
257
+ "d": np.random.randn(50),
258
+ }
259
+ data["b"] = data["a"] + 10 * np.random.randn(50)
260
+ data["d"] = np.abs(data["d"]) * 100
261
+ plt.scatter("a", "b", c="c", s="d", data=data)
262
+ plt.xlabel("entry a")
263
+ plt.ylabel("entry b")
264
+ Plot(plt).save(run, "bar", 2)
265
+
266
+ plt.clf()
267
+
268
+ ax = plt.subplot()
269
+ t = np.arange(0.0, 5.0, 0.01)
270
+ s = np.cos(2 * np.pi * t)
271
+ (line,) = plt.plot(t, s, lw=2)
272
+
273
+ plt.annotate(
274
+ "local max",
275
+ xy=(2, 1),
276
+ xytext=(3, 1.5),
277
+ arrowprops={"facecolor": "black", "shrink": 0.05},
278
+ )
279
+
280
+ plt.ylim(-2, 2)
281
+ Plot(plt.gcf()).save(run, "bar", 3)
@@ -0,0 +1,10 @@
1
+ from truefoundry.pydantic_v1 import BaseModel
2
+
3
+
4
+ class PydanticBase(BaseModel):
5
+ # I can make this a property,
6
+ # but <3.9, it is difficult to access
7
+ # property from classmethod
8
+ @staticmethod
9
+ def get_log_type() -> str:
10
+ raise NotImplementedError()
@@ -0,0 +1,12 @@
1
+ import re
2
+
3
+ from truefoundry.ml.exceptions import MlFoundryException
4
+
5
+ KEY_REGEX = re.compile(r"^[a-zA-Z0-9-_]+$")
6
+
7
+
8
+ def validate_key_name(key: str):
9
+ if not key or not KEY_REGEX.match(key):
10
+ raise MlFoundryException(
11
+ f"Invalid run image key: {key} should only contain alphanumeric, hyphen or underscore"
12
+ )
@@ -0,0 +1,17 @@
1
+ import logging
2
+ import sys
3
+
4
+ logger = logging.getLogger("truefoundry.ml")
5
+
6
+
7
+ def init_logger(level=logging.INFO):
8
+ handler = logging.StreamHandler(sys.stdout)
9
+ handler.setLevel(level)
10
+ formatter = logging.Formatter(
11
+ "[%(name)s] %(asctime)s %(levelname)s %(message)s",
12
+ datefmt="%Y-%m-%dT%H:%M:%S%z",
13
+ )
14
+ handler.setFormatter(formatter)
15
+ logger.addHandler(handler)
16
+ logger.setLevel(logging.DEBUG)
17
+ logger.propagate = False
@@ -0,0 +1,241 @@
1
+ import os
2
+ import threading
3
+ from functools import lru_cache, wraps
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import click
8
+ from filelock import FileLock, Timeout
9
+
10
+ from truefoundry.ml.env_vars import API_KEY_GLOBAL, TRACKING_HOST_GLOBAL
11
+ from truefoundry.ml.exceptions import MlFoundryException
12
+ from truefoundry.ml.logger import logger
13
+ from truefoundry.ml.run_utils import resolve_tracking_uri
14
+ from truefoundry.ml.services.auth_service import get_auth_service
15
+ from truefoundry.ml.services.entities import Token
16
+ from truefoundry.pydantic_v1 import BaseModel, Field, NonEmptyStr
17
+
18
+ CREDENTIALS_DIR = Path.home() / ".truefoundry"
19
+ CREDENTIALS_FILE = CREDENTIALS_DIR / "credentials.json"
20
+
21
+
22
+ OLD_CREDENTIALS_DIR = Path.home() / ".mlfoundry"
23
+ OLD_CREDENTIALS_FILE = OLD_CREDENTIALS_DIR / "credentials.netrc"
24
+
25
+ if OLD_CREDENTIALS_FILE.exists():
26
+ logger.warning(
27
+ "%s file is deprecated. You can delete this file now.", OLD_CREDENTIALS_FILE
28
+ )
29
+
30
+
31
+ class CredentialsFileContent(BaseModel):
32
+ access_token: NonEmptyStr = Field(repr=False)
33
+ refresh_token: Optional[NonEmptyStr] = Field(repr=False)
34
+ host: NonEmptyStr
35
+
36
+ class Config:
37
+ allow_mutation = False
38
+
39
+ def to_token(self) -> Token:
40
+ return Token(
41
+ access_token=self.access_token, # type: ignore[call-arg]
42
+ refresh_token=self.refresh_token,
43
+ )
44
+
45
+
46
+ def _ensure_lock_taken(method):
47
+ @wraps(method)
48
+ def lock_guard(self: "CredentialsFileManager", *method_args, **method_kwargs):
49
+ if not self.lock_taken():
50
+ raise Exception(
51
+ "Trying to write to credential file without using with block"
52
+ )
53
+ return method(self, *method_args, **method_kwargs)
54
+
55
+ return lock_guard
56
+
57
+
58
+ CRED_FILE_THREAD_LOCK = threading.RLock()
59
+
60
+
61
+ @lru_cache(maxsize=None)
62
+ def get_file_lock(lock_file_path: str) -> FileLock:
63
+ return FileLock(lock_file_path)
64
+
65
+
66
+ class CredentialsFileManager:
67
+ def __init__(
68
+ self,
69
+ credentials_file_path: Path = CREDENTIALS_FILE,
70
+ lock_timeout: float = 60.0,
71
+ ):
72
+ credentials_file_path = credentials_file_path.absolute()
73
+
74
+ logger.debug("credential file path %r", credentials_file_path)
75
+
76
+ credentials_lock_file_path = f"{credentials_file_path}.lock"
77
+
78
+ logger.debug("credential lock file path %r", credentials_lock_file_path)
79
+ self._credentials_file_path = credentials_file_path
80
+
81
+ cred_file_dir = credentials_file_path.parent
82
+ cred_file_dir.mkdir(exist_ok=True, parents=True)
83
+
84
+ self._file_lock = get_file_lock(credentials_lock_file_path)
85
+ self._lock_timeout = lock_timeout
86
+ self._lock_owner: Optional[int] = None
87
+
88
+ def __enter__(self) -> "CredentialsFileManager":
89
+ # The lock objects are recursive locks, which means that once acquired,
90
+ # they will not block on successive lock requests:
91
+ lock_aquired = CRED_FILE_THREAD_LOCK.acquire(timeout=self._lock_timeout)
92
+ if not lock_aquired:
93
+ raise MlFoundryException(
94
+ "Could not aquire CRED_FILE_THREAD_LOCK"
95
+ f" in {self._lock_timeout} seconds"
96
+ )
97
+ try:
98
+ self._file_lock.acquire(timeout=self._lock_timeout)
99
+ except Timeout as ex:
100
+ raise MlFoundryException(
101
+ f"Failed to aquire lock on credential file within {self._lock_timeout} seconds.\n"
102
+ "Is any other process trying to login?"
103
+ ) from ex
104
+ logger.debug("Acquired file and thread lock to access credential file")
105
+ self._lock_owner = threading.get_ident()
106
+ return self
107
+
108
+ def __exit__(self, exc_type, exc_val, exc_tb):
109
+ self._file_lock.release()
110
+ CRED_FILE_THREAD_LOCK.release()
111
+ logger.debug("Released file and thread lock to access credential file")
112
+ self._lock_owner = None
113
+
114
+ def lock_taken(self) -> bool:
115
+ return self._lock_owner == threading.get_ident()
116
+
117
+ @_ensure_lock_taken
118
+ def read(self) -> CredentialsFileContent:
119
+ try:
120
+ return CredentialsFileContent.parse_file(self._credentials_file_path)
121
+ except Exception as ex:
122
+ raise MlFoundryException(
123
+ "Error while reading the credentials file "
124
+ f"{self._credentials_file_path}. Please login again "
125
+ "using `tfy login` command or `truefoundry.login()` function"
126
+ ) from ex
127
+
128
+ @_ensure_lock_taken
129
+ def write(self, credentials_file_content: CredentialsFileContent):
130
+ if not isinstance(credentials_file_content, CredentialsFileContent):
131
+ raise MlFoundryException(
132
+ "Only object of type `CredentialsFileContent` is allowed. "
133
+ f"Got {type(credentials_file_content)}"
134
+ )
135
+ logger.debug("Updating the credential file content")
136
+ with open(self._credentials_file_path, "w", encoding="utf8") as file:
137
+ file.write(credentials_file_content.json())
138
+
139
+ @_ensure_lock_taken
140
+ def exists(self) -> bool:
141
+ return self._credentials_file_path.exists()
142
+
143
+
144
+ def login(
145
+ tracking_uri: Optional[str] = None,
146
+ relogin: bool = False,
147
+ api_key: Optional[str] = None,
148
+ ) -> bool:
149
+ """Save API key in local file system for a given `tracking_uri`.
150
+
151
+ Args:
152
+ tracking_uri (Optional[str], optional): tracking_uri for the given API key
153
+ relogin (bool, optional): Overwrites the existing API key for the `tracking_uri` if
154
+ set to `True`. If set to `False` and an API key is already present for
155
+ the given `tracking_uri`, then the existing API key is kept untouched.
156
+ Default is `False`.
157
+ api_key (Optional[str], optional): The API key for the given `tracking_uri`.
158
+ If `api_key` is not passed, this function prompts for the API key.
159
+
160
+ Returns:
161
+ bool: Returns `True` if any credential was persisted.
162
+ """
163
+ from truefoundry.ml.session import EnvCredentialProvider
164
+
165
+ if API_KEY_GLOBAL in os.environ and TRACKING_HOST_GLOBAL in os.environ:
166
+ logger.warning(
167
+ "Skipping login because environment variables %s and "
168
+ "%s are set and will be used when running truefoundry. "
169
+ "If you want to relogin then unset these environment keys.",
170
+ TRACKING_HOST_GLOBAL,
171
+ API_KEY_GLOBAL,
172
+ )
173
+ return False
174
+
175
+ if EnvCredentialProvider.can_provide():
176
+ logger.warning(
177
+ "TFY_API_KEY env var is already set. "
178
+ "When running mlfoundry, it will use the api key to authorize.\n"
179
+ "Login will just save the credentials on disk."
180
+ )
181
+
182
+ tracking_uri = resolve_tracking_uri(tracking_uri).strip("/")
183
+ auth_service = get_auth_service(tracking_uri=tracking_uri)
184
+
185
+ cred_file = CredentialsFileManager()
186
+
187
+ with cred_file:
188
+ if not relogin and cred_file.exists():
189
+ cred_file_content = cred_file.read()
190
+ if tracking_uri != cred_file_content.host:
191
+ if click.confirm(
192
+ f"Already logged in to {cred_file_content.host!r}\n"
193
+ f"Do you want to relogin to {tracking_uri!r}?"
194
+ ):
195
+ return login(
196
+ tracking_uri=tracking_uri, relogin=True, api_key=api_key
197
+ )
198
+ user_info = cred_file_content.to_token().to_user_info()
199
+ user_name_display_info = user_info.email or user_info.user_type.value
200
+ print(
201
+ f"Already logged in to {cred_file_content.host!r} as "
202
+ f"{user_info.user_id!r} ({user_name_display_info!r})\n"
203
+ "Please use `tfy login --relogin` or `truefoundry.login(relogin=True)` "
204
+ "to force relogin"
205
+ )
206
+ return False
207
+
208
+ if api_key:
209
+ logger.debug("Logging in with api key")
210
+ token = Token(access_token=api_key, refresh_token=None) # type: ignore[call-arg]
211
+ cred_file_content = CredentialsFileContent(
212
+ access_token=token.access_token,
213
+ refresh_token=token.refresh_token,
214
+ host=tracking_uri,
215
+ )
216
+ cred_file.write(cred_file_content)
217
+ else:
218
+ device_code = auth_service.get_device_code()
219
+ url_to_go = device_code.get_user_clickable_url(tracking_uri=tracking_uri)
220
+ print(f"Opening:- {url_to_go}")
221
+ print(
222
+ "Please click on the above link if it is not "
223
+ "automatically opened in a browser window."
224
+ )
225
+ click.launch(url_to_go)
226
+ token = auth_service.get_token_from_device_code(
227
+ device_code=device_code.device_code, timeout=120
228
+ )
229
+ cred_file_content = CredentialsFileContent(
230
+ access_token=token.access_token,
231
+ refresh_token=token.refresh_token,
232
+ host=tracking_uri,
233
+ )
234
+ cred_file.write(cred_file_content)
235
+
236
+ user_info = token.to_user_info()
237
+ user_name_display_info = user_info.email or user_info.user_type.value
238
+ print(
239
+ f"Logged in to {cred_file_content.host!r} as {user_info.user_id!r} ({user_name_display_info})"
240
+ )
241
+ return True