truefoundry 0.3.3__py3-none-any.whl → 0.4.0.dev1__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.
- truefoundry/cli/__main__.py +3 -17
- truefoundry/common/__init__.py +0 -0
- truefoundry/common/request_utils.py +56 -0
- truefoundry/deploy/cli/cli.py +1 -1
- truefoundry/deploy/cli/util.py +3 -1
- truefoundry/deploy/lib/auth/credential_provider.py +2 -12
- truefoundry/deploy/lib/clients/servicefoundry_client.py +0 -9
- truefoundry/deploy/lib/exceptions.py +1 -6
- truefoundry/deploy/lib/session.py +1 -16
- truefoundry/langchain/truefoundry_chat.py +1 -1
- truefoundry/langchain/truefoundry_embeddings.py +1 -1
- truefoundry/langchain/truefoundry_llm.py +1 -1
- truefoundry/langchain/utils.py +0 -41
- truefoundry/ml/__init__.py +46 -6
- truefoundry/ml/artifact/__init__.py +0 -0
- truefoundry/ml/artifact/truefoundry_artifact_repo.py +1120 -0
- truefoundry/ml/autogen/__init__.py +0 -0
- truefoundry/ml/autogen/client/__init__.py +373 -0
- truefoundry/ml/autogen/client/api/__init__.py +16 -0
- truefoundry/ml/autogen/client/api/auth_api.py +184 -0
- truefoundry/ml/autogen/client/api/deprecated_api.py +605 -0
- truefoundry/ml/autogen/client/api/experiments_api.py +2109 -0
- truefoundry/ml/autogen/client/api/health_api.py +299 -0
- truefoundry/ml/autogen/client/api/metrics_api.py +371 -0
- truefoundry/ml/autogen/client/api/mlfoundry_artifacts_api.py +7213 -0
- truefoundry/ml/autogen/client/api/python_deployment_config_api.py +201 -0
- truefoundry/ml/autogen/client/api/run_artifacts_api.py +231 -0
- truefoundry/ml/autogen/client/api/runs_api.py +2919 -0
- truefoundry/ml/autogen/client/api_client.py +822 -0
- truefoundry/ml/autogen/client/api_response.py +30 -0
- truefoundry/ml/autogen/client/configuration.py +489 -0
- truefoundry/ml/autogen/client/exceptions.py +161 -0
- truefoundry/ml/autogen/client/models/__init__.py +344 -0
- truefoundry/ml/autogen/client/models/add_custom_metrics_to_model_version_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/add_features_to_model_version_request_dto.py +83 -0
- truefoundry/ml/autogen/client/models/agent.py +125 -0
- truefoundry/ml/autogen/client/models/agent_app.py +118 -0
- truefoundry/ml/autogen/client/models/agent_open_api_tool.py +143 -0
- truefoundry/ml/autogen/client/models/agent_open_api_tool_with_fqn.py +144 -0
- truefoundry/ml/autogen/client/models/agent_with_fqn.py +127 -0
- truefoundry/ml/autogen/client/models/artifact_dto.py +115 -0
- truefoundry/ml/autogen/client/models/artifact_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/artifact_type.py +39 -0
- truefoundry/ml/autogen/client/models/artifact_version_dto.py +141 -0
- truefoundry/ml/autogen/client/models/artifact_version_response_dto.py +77 -0
- truefoundry/ml/autogen/client/models/artifact_version_status.py +35 -0
- truefoundry/ml/autogen/client/models/assistant_message.py +89 -0
- truefoundry/ml/autogen/client/models/authorize_user_for_model_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/authorize_user_for_model_version_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/backfill_default_storage_integration_id_request_dto.py +67 -0
- truefoundry/ml/autogen/client/models/blob_storage_reference.py +93 -0
- truefoundry/ml/autogen/client/models/body_get_search_runs_get.py +72 -0
- truefoundry/ml/autogen/client/models/chat_prompt.py +156 -0
- truefoundry/ml/autogen/client/models/chat_prompt_messages_inner.py +171 -0
- truefoundry/ml/autogen/client/models/columns_dto.py +73 -0
- truefoundry/ml/autogen/client/models/content.py +153 -0
- truefoundry/ml/autogen/client/models/content1.py +153 -0
- truefoundry/ml/autogen/client/models/content2.py +174 -0
- truefoundry/ml/autogen/client/models/content2_any_of_inner.py +150 -0
- truefoundry/ml/autogen/client/models/create_artifact_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +66 -0
- truefoundry/ml/autogen/client/models/create_artifact_version_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +66 -0
- truefoundry/ml/autogen/client/models/create_dataset_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/create_experiment_request_dto.py +94 -0
- truefoundry/ml/autogen/client/models/create_experiment_response_dto.py +67 -0
- truefoundry/ml/autogen/client/models/create_model_version_request_dto.py +95 -0
- truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_request_dto.py +73 -0
- truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_response_dto.py +79 -0
- truefoundry/ml/autogen/client/models/create_multi_part_upload_request_dto.py +73 -0
- truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +72 -0
- truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +68 -0
- truefoundry/ml/autogen/client/models/create_run_request_dto.py +97 -0
- truefoundry/ml/autogen/client/models/create_run_response_dto.py +76 -0
- truefoundry/ml/autogen/client/models/dataset_dto.py +112 -0
- truefoundry/ml/autogen/client/models/dataset_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/delete_artifact_versions_request_dto.py +65 -0
- truefoundry/ml/autogen/client/models/delete_dataset_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/delete_model_version_request_dto.py +65 -0
- truefoundry/ml/autogen/client/models/delete_run_request.py +65 -0
- truefoundry/ml/autogen/client/models/delete_tag_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/experiment_dto.py +127 -0
- truefoundry/ml/autogen/client/models/experiment_id_request_dto.py +67 -0
- truefoundry/ml/autogen/client/models/experiment_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/experiment_tag_dto.py +69 -0
- truefoundry/ml/autogen/client/models/feature_dto.py +68 -0
- truefoundry/ml/autogen/client/models/feature_value_type.py +35 -0
- truefoundry/ml/autogen/client/models/file_info_dto.py +76 -0
- truefoundry/ml/autogen/client/models/finalize_artifact_version_request_dto.py +101 -0
- truefoundry/ml/autogen/client/models/get_experiment_response_dto.py +88 -0
- truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +76 -0
- truefoundry/ml/autogen/client/models/get_metric_history_response.py +79 -0
- truefoundry/ml/autogen/client/models/get_signed_url_for_dataset_write_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_response_dto.py +83 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_write_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +74 -0
- truefoundry/ml/autogen/client/models/http_validation_error.py +82 -0
- truefoundry/ml/autogen/client/models/image_content_part.py +87 -0
- truefoundry/ml/autogen/client/models/image_url.py +75 -0
- truefoundry/ml/autogen/client/models/internal_metadata.py +180 -0
- truefoundry/ml/autogen/client/models/latest_run_log_dto.py +78 -0
- truefoundry/ml/autogen/client/models/list_artifact_versions_request_dto.py +107 -0
- truefoundry/ml/autogen/client/models/list_artifact_versions_response_dto.py +87 -0
- truefoundry/ml/autogen/client/models/list_artifacts_request_dto.py +96 -0
- truefoundry/ml/autogen/client/models/list_artifacts_response_dto.py +86 -0
- truefoundry/ml/autogen/client/models/list_colums_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/list_datasets_request_dto.py +78 -0
- truefoundry/ml/autogen/client/models/list_datasets_response_dto.py +86 -0
- truefoundry/ml/autogen/client/models/list_experiments_response_dto.py +86 -0
- truefoundry/ml/autogen/client/models/list_files_for_artifact_version_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/list_files_for_artifact_versions_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_files_for_dataset_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/list_files_for_dataset_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_latest_run_logs_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_metric_history_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/list_metric_history_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/list_model_version_response_dto.py +87 -0
- truefoundry/ml/autogen/client/models/list_model_versions_request_dto.py +93 -0
- truefoundry/ml/autogen/client/models/list_models_request_dto.py +89 -0
- truefoundry/ml/autogen/client/models/list_models_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/list_run_artifacts_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/list_run_logs_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_seed_experiments_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/log_batch_request_dto.py +106 -0
- truefoundry/ml/autogen/client/models/log_metric_request_dto.py +80 -0
- truefoundry/ml/autogen/client/models/log_param_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/method.py +37 -0
- truefoundry/ml/autogen/client/models/metric_collection_dto.py +82 -0
- truefoundry/ml/autogen/client/models/metric_dto.py +76 -0
- truefoundry/ml/autogen/client/models/mime_type.py +37 -0
- truefoundry/ml/autogen/client/models/model_configuration.py +103 -0
- truefoundry/ml/autogen/client/models/model_dto.py +122 -0
- truefoundry/ml/autogen/client/models/model_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/model_schema_dto.py +85 -0
- truefoundry/ml/autogen/client/models/model_version_dto.py +163 -0
- truefoundry/ml/autogen/client/models/model_version_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/multi_part_upload_dto.py +107 -0
- truefoundry/ml/autogen/client/models/multi_part_upload_response_dto.py +79 -0
- truefoundry/ml/autogen/client/models/multi_part_upload_storage_provider.py +34 -0
- truefoundry/ml/autogen/client/models/notify_artifact_version_failure_dto.py +65 -0
- truefoundry/ml/autogen/client/models/openapi_spec.py +152 -0
- truefoundry/ml/autogen/client/models/param_dto.py +66 -0
- truefoundry/ml/autogen/client/models/parameters.py +84 -0
- truefoundry/ml/autogen/client/models/prediction_type.py +34 -0
- truefoundry/ml/autogen/client/models/resolve_agent_app_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/restore_run_request_dto.py +65 -0
- truefoundry/ml/autogen/client/models/run_data_dto.py +104 -0
- truefoundry/ml/autogen/client/models/run_dto.py +84 -0
- truefoundry/ml/autogen/client/models/run_info_dto.py +105 -0
- truefoundry/ml/autogen/client/models/run_log_dto.py +90 -0
- truefoundry/ml/autogen/client/models/run_log_input_dto.py +80 -0
- truefoundry/ml/autogen/client/models/run_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/run_tag_dto.py +66 -0
- truefoundry/ml/autogen/client/models/search_runs_request_dto.py +94 -0
- truefoundry/ml/autogen/client/models/search_runs_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/set_experiment_tag_request_dto.py +73 -0
- truefoundry/ml/autogen/client/models/set_tag_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/signed_url_dto.py +69 -0
- truefoundry/ml/autogen/client/models/stop.py +152 -0
- truefoundry/ml/autogen/client/models/store_run_logs_request_dto.py +83 -0
- truefoundry/ml/autogen/client/models/system_message.py +89 -0
- truefoundry/ml/autogen/client/models/text.py +153 -0
- truefoundry/ml/autogen/client/models/text_content_part.py +84 -0
- truefoundry/ml/autogen/client/models/update_artifact_version_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/update_dataset_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/update_experiment_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/update_model_version_request_dto.py +93 -0
- truefoundry/ml/autogen/client/models/update_run_request_dto.py +78 -0
- truefoundry/ml/autogen/client/models/update_run_response_dto.py +76 -0
- truefoundry/ml/autogen/client/models/url.py +153 -0
- truefoundry/ml/autogen/client/models/user_message.py +89 -0
- truefoundry/ml/autogen/client/models/validation_error.py +87 -0
- truefoundry/ml/autogen/client/models/validation_error_loc_inner.py +154 -0
- truefoundry/ml/autogen/client/rest.py +426 -0
- truefoundry/ml/autogen/client_README.md +322 -0
- truefoundry/ml/cli/__init__.py +0 -0
- truefoundry/ml/cli/cli.py +18 -0
- truefoundry/ml/cli/commands/__init__.py +3 -0
- truefoundry/ml/cli/commands/download.py +87 -0
- truefoundry/ml/constants.py +84 -0
- truefoundry/ml/enums.py +70 -0
- truefoundry/ml/env_vars.py +13 -0
- truefoundry/ml/exceptions.py +8 -0
- truefoundry/ml/git_info.py +60 -0
- truefoundry/ml/internal_namespace.py +52 -0
- truefoundry/ml/log_types/__init__.py +4 -0
- truefoundry/ml/log_types/artifacts/artifact.py +427 -0
- truefoundry/ml/log_types/artifacts/constants.py +33 -0
- truefoundry/ml/log_types/artifacts/dataset.py +383 -0
- truefoundry/ml/log_types/artifacts/general_artifact.py +110 -0
- truefoundry/ml/log_types/artifacts/model.py +628 -0
- truefoundry/ml/log_types/artifacts/model_extras.py +48 -0
- truefoundry/ml/log_types/artifacts/utils.py +161 -0
- truefoundry/ml/log_types/image/__init__.py +3 -0
- truefoundry/ml/log_types/image/constants.py +8 -0
- truefoundry/ml/log_types/image/image.py +358 -0
- truefoundry/ml/log_types/image/image_normalizer.py +101 -0
- truefoundry/ml/log_types/image/types.py +68 -0
- truefoundry/ml/log_types/plot.py +281 -0
- truefoundry/ml/log_types/pydantic_base.py +10 -0
- truefoundry/ml/log_types/utils.py +12 -0
- truefoundry/ml/logger.py +17 -0
- truefoundry/ml/login.py +241 -0
- truefoundry/ml/mlfoundry_api.py +1620 -0
- truefoundry/ml/mlfoundry_run.py +1238 -0
- truefoundry/ml/run_utils.py +102 -0
- truefoundry/ml/services/__init__.py +0 -0
- truefoundry/ml/services/auth_service.py +109 -0
- truefoundry/ml/services/entities.py +108 -0
- truefoundry/ml/services/servicefoundry_service.py +35 -0
- truefoundry/ml/services/utils.py +122 -0
- truefoundry/ml/session.py +271 -0
- truefoundry/ml/validation_utils.py +346 -0
- truefoundry/pydantic_v1.py +5 -1
- {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev1.dist-info}/METADATA +18 -11
- truefoundry-0.4.0.dev1.dist-info/RECORD +342 -0
- truefoundry-0.3.3.dist-info/RECORD +0 -136
- /truefoundry/{python_deploy_codegen.py → deploy/python_deploy_codegen.py} +0 -0
- {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev1.dist-info}/WHEEL +0 -0
- {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev1.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,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
|
+
)
|
truefoundry/ml/logger.py
ADDED
|
@@ -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
|
truefoundry/ml/login.py
ADDED
|
@@ -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
|