flytekitplugins-comet-ml 1.13.1a1__tar.gz

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.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: flytekitplugins-comet-ml
3
+ Version: 1.13.1a1
4
+ Summary: This package enables seamless use of Comet within Flyte
5
+ Author: flyteorg
6
+ Author-email: admin@flyte.org
7
+ License: apache2
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.8
22
+ Requires-Dist: flytekit>=1.12.3
23
+ Requires-Dist: comet-ml>=3.43.2
@@ -0,0 +1,26 @@
1
+ # Flytekit Comet Plugin
2
+
3
+ Comet’s machine learning platform integrates with your existing infrastructure and tools so you can manage, visualize, and optimize models—from training runs to production monitoring. This plugin integrates Flyte with Comet.ml by configuring links between the two platforms.
4
+
5
+ To install the plugin, run:
6
+
7
+ ```bash
8
+ pip install flytekitplugins-comet-ml
9
+ ```
10
+
11
+ Comet requires an API key to authenticate with their platform. In the above example, a secret is created using
12
+ [Flyte's Secrets manager](https://docs.flyte.org/en/latest/user_guide/productionizing/secrets.html).
13
+
14
+ To enable linking from the Flyte side panel to Comet.ml, add the following to Flyte's configuration:
15
+
16
+ ```yaml
17
+ plugins:
18
+ logs:
19
+ dynamic-log-links:
20
+ - comet-ml-execution-id:
21
+ displayName: Comet
22
+ templateUris: "{{ .taskConfig.host }}/{{ .taskConfig.workspace }}/{{ .taskConfig.project_name }}/{{ .executionName }}{{ .nodeId }}{{ .taskRetryAttempt }}{{ .taskConfig.link_suffix }}"
23
+ - comet-ml-custom-id:
24
+ displayName: Comet
25
+ templateUris: "{{ .taskConfig.host }}/{{ .taskConfig.workspace }}/{{ .taskConfig.project_name }}/{{ .taskConfig.experiment_key }}"
26
+ ```
@@ -0,0 +1,3 @@
1
+ from .tracking import comet_ml_login
2
+
3
+ __all__ = ["comet_ml_login"]
@@ -0,0 +1,173 @@
1
+ import os
2
+ from functools import partial
3
+ from hashlib import shake_256
4
+ from typing import Callable, Optional, Union
5
+
6
+ import comet_ml
7
+ from flytekit import Secret
8
+ from flytekit.core.context_manager import FlyteContextManager
9
+ from flytekit.core.utils import ClassDecorator
10
+
11
+ COMET_ML_EXECUTION_TYPE_VALUE = "comet-ml-execution-id"
12
+ COMET_ML_CUSTOM_TYPE_VALUE = "comet-ml-custom-id"
13
+
14
+
15
+ def _generate_suffix_with_length_10(project_name: str, workspace: str) -> str:
16
+ """Generate suffix from project_name + workspace."""
17
+ h = shake_256(f"{project_name}-{workspace}".encode("utf-8"))
18
+ # Using 5 generates a suffix with length 10
19
+ return h.hexdigest(5)
20
+
21
+
22
+ def _generate_experiment_key(hostname: str, project_name: str, workspace: str) -> str:
23
+ """Generate experiment key that comet_ml can use:
24
+
25
+ 1. Is alphanumeric
26
+ 2. 32 <= len(experiment_key) <= 50
27
+ """
28
+ # In Flyte, then hostname is set to {.executionName}-{.nodeID}-{.taskRetryAttempt}, where
29
+ # - len(executionName) == 20
30
+ # - 2 <= len(nodeId) <= 8
31
+ # - 1 <= len(taskRetryAttempt)) <= 2 (In practice, retries does not go above 99)
32
+ # Removing the `-` because it is not alphanumeric, the 23 <= len(hostname) <= 30
33
+ # On the low end we need to add 10 characters to stay in the range acceptable to comet_ml
34
+ hostname = hostname.replace("-", "")
35
+ suffix = _generate_suffix_with_length_10(project_name, workspace)
36
+ return f"{hostname}{suffix}"
37
+
38
+
39
+ def comet_ml_login(
40
+ project_name: str,
41
+ workspace: str,
42
+ secret: Union[Secret, Callable],
43
+ experiment_key: Optional[str] = None,
44
+ host: str = "https://www.comet.com",
45
+ **login_kwargs: dict,
46
+ ):
47
+ """Comet plugin.
48
+ Args:
49
+ project_name (str): Send your experiment to a specific project. (Required)
50
+ workspace (str): Attach an experiment to a project that belongs to this workspace. (Required)
51
+ secret (Secret or Callable): Secret with your `COMET_API_KEY` or a callable that returns the API key.
52
+ The callable takes no arguments and returns a string. (Required)
53
+ experiment_key (str): Experiment key.
54
+ host (str): URL to your Comet service. Defaults to "https://www.comet.com"
55
+ **login_kwargs (dict): The rest of the arguments are passed directly to `comet_ml.login`.
56
+ """
57
+ return partial(
58
+ _comet_ml_login_class,
59
+ project_name=project_name,
60
+ workspace=workspace,
61
+ secret=secret,
62
+ experiment_key=experiment_key,
63
+ host=host,
64
+ **login_kwargs,
65
+ )
66
+
67
+
68
+ class _comet_ml_login_class(ClassDecorator):
69
+ COMET_ML_PROJECT_NAME_KEY = "project_name"
70
+ COMET_ML_WORKSPACE_KEY = "workspace"
71
+ COMET_ML_EXPERIMENT_KEY_KEY = "experiment_key"
72
+ COMET_ML_URL_SUFFIX_KEY = "link_suffix"
73
+ COMET_ML_HOST_KEY = "host"
74
+
75
+ def __init__(
76
+ self,
77
+ task_function: Callable,
78
+ project_name: str,
79
+ workspace: str,
80
+ secret: Union[Secret, Callable],
81
+ experiment_key: Optional[str] = None,
82
+ host: str = "https://www.comet.com",
83
+ **login_kwargs: dict,
84
+ ):
85
+ """Comet plugin.
86
+ Args:
87
+ project_name (str): Send your experiment to a specific project. (Required)
88
+ workspace (str): Attach an experiment to a project that belongs to this workspace. (Required)
89
+ secret (Secret or Callable): Secret with your `COMET_API_KEY` or a callable that returns the API key.
90
+ The callable takes no arguments and returns a string. (Required)
91
+ experiment_key (str): Experiment key.
92
+ host (str): URL to your Comet service. Defaults to "https://www.comet.com"
93
+ **login_kwargs (dict): The rest of the arguments are passed directly to `comet_ml.login`.
94
+ """
95
+
96
+ self.project_name = project_name
97
+ self.workspace = workspace
98
+ self.experiment_key = experiment_key
99
+ self.secret = secret
100
+ self.host = host
101
+ self.login_kwargs = login_kwargs
102
+
103
+ super().__init__(
104
+ task_function,
105
+ project_name=project_name,
106
+ workspace=workspace,
107
+ experiment_key=experiment_key,
108
+ secret=secret,
109
+ host=host,
110
+ **login_kwargs,
111
+ )
112
+
113
+ def execute(self, *args, **kwargs):
114
+ ctx = FlyteContextManager.current_context()
115
+ is_local_execution = ctx.execution_state.is_local_execution()
116
+
117
+ default_kwargs = self.login_kwargs
118
+ login_kwargs = {
119
+ "project_name": self.project_name,
120
+ "workspace": self.workspace,
121
+ **default_kwargs,
122
+ }
123
+
124
+ if is_local_execution:
125
+ # For local execution, always use the experiment_key. If `self.experiment_key` is `None`, comet_ml
126
+ # will generate it's own key
127
+ if self.experiment_key is not None:
128
+ login_kwargs["experiment_key"] = self.experiment_key
129
+ else:
130
+ # Get api key for remote execution
131
+ if isinstance(self.secret, Secret):
132
+ secrets = ctx.user_space_params.secrets
133
+ comet_ml_api_key = secrets.get(key=self.secret.key, group=self.secret.group)
134
+ else:
135
+ comet_ml_api_key = self.secret()
136
+
137
+ login_kwargs["api_key"] = comet_ml_api_key
138
+
139
+ if self.experiment_key is None:
140
+ # The HOSTNAME is set to {.executionName}-{.nodeID}-{.taskRetryAttempt}
141
+ # If HOSTNAME is not defined, use the execution name as a fallback
142
+ hostname = os.environ.get("HOSTNAME", ctx.user_space_params.execution_id.name)
143
+ experiment_key = _generate_experiment_key(hostname, self.project_name, self.workspace)
144
+ else:
145
+ experiment_key = self.experiment_key
146
+
147
+ login_kwargs["experiment_key"] = experiment_key
148
+
149
+ if hasattr(comet_ml, "login"):
150
+ comet_ml.login(**login_kwargs)
151
+ else:
152
+ comet_ml.init(**login_kwargs)
153
+
154
+ output = self.task_function(*args, **kwargs)
155
+ return output
156
+
157
+ def get_extra_config(self):
158
+ extra_config = {
159
+ self.COMET_ML_PROJECT_NAME_KEY: self.project_name,
160
+ self.COMET_ML_WORKSPACE_KEY: self.workspace,
161
+ self.COMET_ML_HOST_KEY: self.host,
162
+ }
163
+
164
+ if self.experiment_key is None:
165
+ comet_ml_value = COMET_ML_EXECUTION_TYPE_VALUE
166
+ suffix = _generate_suffix_with_length_10(self.project_name, self.workspace)
167
+ extra_config[self.COMET_ML_URL_SUFFIX_KEY] = suffix
168
+ else:
169
+ comet_ml_value = COMET_ML_CUSTOM_TYPE_VALUE
170
+ extra_config[self.COMET_ML_EXPERIMENT_KEY_KEY] = self.experiment_key
171
+
172
+ extra_config[self.LINK_TYPE_KEY] = comet_ml_value
173
+ return extra_config
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: flytekitplugins-comet-ml
3
+ Version: 1.13.1a1
4
+ Summary: This package enables seamless use of Comet within Flyte
5
+ Author: flyteorg
6
+ Author-email: admin@flyte.org
7
+ License: apache2
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.8
22
+ Requires-Dist: flytekit>=1.12.3
23
+ Requires-Dist: comet-ml>=3.43.2
@@ -0,0 +1,11 @@
1
+ README.md
2
+ setup.py
3
+ flytekitplugins/comet_ml/__init__.py
4
+ flytekitplugins/comet_ml/tracking.py
5
+ flytekitplugins_comet_ml.egg-info/PKG-INFO
6
+ flytekitplugins_comet_ml.egg-info/SOURCES.txt
7
+ flytekitplugins_comet_ml.egg-info/dependency_links.txt
8
+ flytekitplugins_comet_ml.egg-info/namespace_packages.txt
9
+ flytekitplugins_comet_ml.egg-info/requires.txt
10
+ flytekitplugins_comet_ml.egg-info/top_level.txt
11
+ tests/test_comet_ml_init.py
@@ -0,0 +1,2 @@
1
+ flytekit>=1.12.3
2
+ comet-ml>=3.43.2
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,39 @@
1
+ from setuptools import setup
2
+
3
+ PLUGIN_NAME = "comet-ml"
4
+ MODULE_NAME = "comet_ml"
5
+
6
+
7
+ microlib_name = f"flytekitplugins-{PLUGIN_NAME}"
8
+
9
+ plugin_requires = ["flytekit>=1.12.3", "comet-ml>=3.43.2"]
10
+
11
+ __version__ = "1.13.1a1"
12
+
13
+ setup(
14
+ name=microlib_name,
15
+ version=__version__,
16
+ author="flyteorg",
17
+ author_email="admin@flyte.org",
18
+ description="This package enables seamless use of Comet within Flyte",
19
+ namespace_packages=["flytekitplugins"],
20
+ packages=[f"flytekitplugins.{MODULE_NAME}"],
21
+ install_requires=plugin_requires,
22
+ license="apache2",
23
+ python_requires=">=3.8",
24
+ classifiers=[
25
+ "Intended Audience :: Science/Research",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: Apache Software License",
28
+ "Programming Language :: Python :: 3.8",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Scientific/Engineering",
34
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
35
+ "Topic :: Software Development",
36
+ "Topic :: Software Development :: Libraries",
37
+ "Topic :: Software Development :: Libraries :: Python Modules",
38
+ ],
39
+ )
@@ -0,0 +1,153 @@
1
+ from hashlib import shake_256
2
+ from unittest.mock import patch, Mock
3
+ import pytest
4
+
5
+ from flytekit import Secret, task
6
+ from flytekitplugins.comet_ml import comet_ml_login
7
+ from flytekitplugins.comet_ml.tracking import (
8
+ COMET_ML_CUSTOM_TYPE_VALUE,
9
+ COMET_ML_EXECUTION_TYPE_VALUE,
10
+ _generate_suffix_with_length_10,
11
+ _generate_experiment_key,
12
+ )
13
+
14
+
15
+ secret = Secret(key="abc", group="xyz")
16
+
17
+
18
+ @pytest.mark.parametrize("experiment_key", [None, "abc123dfassfasfsafsafd"])
19
+ def test_extra_config(experiment_key):
20
+ project_name = "abc"
21
+ workspace = "my_workspace"
22
+
23
+ comet_decorator = comet_ml_login(
24
+ project_name=project_name,
25
+ workspace=workspace,
26
+ experiment_key=experiment_key,
27
+ secret=secret
28
+ )
29
+
30
+ @comet_decorator
31
+ def task():
32
+ pass
33
+
34
+ assert task.secret is secret
35
+ extra_config = task.get_extra_config()
36
+
37
+ if experiment_key is None:
38
+ assert extra_config[task.LINK_TYPE_KEY] == COMET_ML_EXECUTION_TYPE_VALUE
39
+ assert task.COMET_ML_EXPERIMENT_KEY_KEY not in extra_config
40
+
41
+ suffix = _generate_suffix_with_length_10(project_name=project_name, workspace=workspace)
42
+ assert extra_config[task.COMET_ML_URL_SUFFIX_KEY] == suffix
43
+
44
+ else:
45
+ assert extra_config[task.LINK_TYPE_KEY] == COMET_ML_CUSTOM_TYPE_VALUE
46
+ assert extra_config[task.COMET_ML_EXPERIMENT_KEY_KEY] == experiment_key
47
+ assert task.COMET_ML_URL_SUFFIX_KEY not in extra_config
48
+
49
+ assert extra_config[task.COMET_ML_WORKSPACE_KEY] == workspace
50
+ assert extra_config[task.COMET_ML_HOST_KEY] == "https://www.comet.com"
51
+
52
+
53
+ @task
54
+ @comet_ml_login(project_name="abc", workspace="my-workspace", secret=secret, log_code=False)
55
+ def train_model():
56
+ pass
57
+
58
+
59
+ @patch("flytekitplugins.comet_ml.tracking.comet_ml")
60
+ def test_local_execution(comet_ml_mock):
61
+ train_model()
62
+
63
+ comet_ml_mock.login.assert_called_with(
64
+ project_name="abc", workspace="my-workspace", log_code=False)
65
+
66
+
67
+ @task
68
+ @comet_ml_login(
69
+ project_name="xyz",
70
+ workspace="another-workspace",
71
+ secret=secret,
72
+ experiment_key="my-previous-experiment-key",
73
+ )
74
+ def train_model_with_experiment_key():
75
+ pass
76
+
77
+
78
+ @patch("flytekitplugins.comet_ml.tracking.comet_ml")
79
+ def test_local_execution_with_experiment_key(comet_ml_mock):
80
+ train_model_with_experiment_key()
81
+
82
+ comet_ml_mock.login.assert_called_with(
83
+ project_name="xyz",
84
+ workspace="another-workspace",
85
+ experiment_key="my-previous-experiment-key",
86
+ )
87
+
88
+
89
+ @patch("flytekitplugins.comet_ml.tracking.os")
90
+ @patch("flytekitplugins.comet_ml.tracking.FlyteContextManager")
91
+ @patch("flytekitplugins.comet_ml.tracking.comet_ml")
92
+ def test_remote_execution(comet_ml_mock, manager_mock, os_mock):
93
+ # Pretend that the execution is remote
94
+ ctx_mock = Mock()
95
+ ctx_mock.execution_state.is_local_execution.return_value = False
96
+
97
+ ctx_mock.user_space_params.secrets.get.return_value = "this_is_the_secret"
98
+ ctx_mock.user_space_params.execution_id.name = "my_execution_id"
99
+
100
+ manager_mock.current_context.return_value = ctx_mock
101
+ hostname = "a423423423afasf4jigl-fasj4321-0"
102
+ os_mock.environ = {"HOSTNAME": hostname}
103
+
104
+ project_name = "abc"
105
+ workspace = "my-workspace"
106
+
107
+ h = shake_256(f"{project_name}-{workspace}".encode("utf-8"))
108
+ suffix = h.hexdigest(5)
109
+ hostname_alpha = hostname.replace("-", "")
110
+ experiment_key = f"{hostname_alpha}{suffix}"
111
+
112
+ train_model()
113
+
114
+ comet_ml_mock.login.assert_called_with(
115
+ project_name="abc",
116
+ workspace="my-workspace",
117
+ api_key="this_is_the_secret",
118
+ experiment_key=experiment_key,
119
+ log_code=False,
120
+ )
121
+ ctx_mock.user_space_params.secrets.get.assert_called_with(key="abc", group="xyz")
122
+
123
+
124
+ def get_secret():
125
+ return "my-comet-ml-api-key"
126
+
127
+
128
+ @task
129
+ @comet_ml_login(project_name="my_project", workspace="my_workspace", secret=get_secret)
130
+ def train_model_with_callable_secret():
131
+ pass
132
+
133
+
134
+ @patch("flytekitplugins.comet_ml.tracking.os")
135
+ @patch("flytekitplugins.comet_ml.tracking.FlyteContextManager")
136
+ @patch("flytekitplugins.comet_ml.tracking.comet_ml")
137
+ def test_remote_execution_with_callable_secret(comet_ml_mock, manager_mock, os_mock):
138
+ # Pretend that the execution is remote
139
+ ctx_mock = Mock()
140
+ ctx_mock.execution_state.is_local_execution.return_value = False
141
+
142
+ manager_mock.current_context.return_value = ctx_mock
143
+ hostname = "a423423423afasf4jigl-fasj4321-0"
144
+ os_mock.environ = {"HOSTNAME": hostname}
145
+
146
+ train_model_with_callable_secret()
147
+
148
+ comet_ml_mock.login.assert_called_with(
149
+ project_name="my_project",
150
+ api_key="my-comet-ml-api-key",
151
+ workspace="my_workspace",
152
+ experiment_key=_generate_experiment_key(hostname, "my_project", "my_workspace")
153
+ )