metaflow 2.12.27__py2.py3-none-any.whl → 2.12.29__py2.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.
Files changed (63) hide show
  1. metaflow/__init__.py +2 -3
  2. metaflow/cli.py +50 -13
  3. metaflow/client/core.py +2 -2
  4. metaflow/clone_util.py +1 -1
  5. metaflow/cmd/develop/stub_generator.py +623 -233
  6. metaflow/datastore/task_datastore.py +1 -1
  7. metaflow/extension_support/plugins.py +1 -0
  8. metaflow/flowspec.py +2 -2
  9. metaflow/includefile.py +8 -14
  10. metaflow/metaflow_config.py +4 -0
  11. metaflow/metaflow_current.py +1 -1
  12. metaflow/parameters.py +3 -0
  13. metaflow/plugins/__init__.py +12 -3
  14. metaflow/plugins/airflow/airflow_cli.py +5 -0
  15. metaflow/plugins/airflow/airflow_decorator.py +1 -1
  16. metaflow/plugins/argo/argo_workflows_decorator.py +1 -1
  17. metaflow/plugins/argo/argo_workflows_deployer.py +77 -263
  18. metaflow/plugins/argo/argo_workflows_deployer_objects.py +381 -0
  19. metaflow/plugins/aws/batch/batch_cli.py +1 -1
  20. metaflow/plugins/aws/batch/batch_decorator.py +2 -2
  21. metaflow/plugins/aws/step_functions/step_functions_cli.py +7 -0
  22. metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -1
  23. metaflow/plugins/aws/step_functions/step_functions_deployer.py +65 -224
  24. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +236 -0
  25. metaflow/plugins/azure/includefile_support.py +2 -0
  26. metaflow/plugins/cards/card_cli.py +3 -2
  27. metaflow/plugins/cards/card_modules/components.py +9 -9
  28. metaflow/plugins/cards/card_server.py +39 -14
  29. metaflow/plugins/datatools/local.py +2 -0
  30. metaflow/plugins/datatools/s3/s3.py +2 -0
  31. metaflow/plugins/env_escape/__init__.py +3 -3
  32. metaflow/plugins/gcp/includefile_support.py +3 -0
  33. metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
  34. metaflow/plugins/kubernetes/kubernetes_decorator.py +5 -4
  35. metaflow/plugins/kubernetes/kubernetes_jobsets.py +1 -4
  36. metaflow/plugins/{metadata → metadata_providers}/local.py +2 -2
  37. metaflow/plugins/{metadata → metadata_providers}/service.py +2 -2
  38. metaflow/plugins/parallel_decorator.py +1 -1
  39. metaflow/plugins/pypi/conda_decorator.py +1 -1
  40. metaflow/plugins/test_unbounded_foreach_decorator.py +1 -1
  41. metaflow/runner/click_api.py +4 -0
  42. metaflow/runner/deployer.py +139 -269
  43. metaflow/runner/deployer_impl.py +167 -0
  44. metaflow/runner/metaflow_runner.py +10 -9
  45. metaflow/runner/nbdeploy.py +12 -13
  46. metaflow/runner/nbrun.py +3 -3
  47. metaflow/runner/utils.py +55 -8
  48. metaflow/runtime.py +1 -1
  49. metaflow/system/system_logger.py +1 -19
  50. metaflow/system/system_monitor.py +0 -24
  51. metaflow/task.py +5 -8
  52. metaflow/version.py +1 -1
  53. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/METADATA +2 -2
  54. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/RECORD +63 -60
  55. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/WHEEL +1 -1
  56. /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
  57. /metaflow/{metadata → metadata_provider}/heartbeat.py +0 -0
  58. /metaflow/{metadata → metadata_provider}/metadata.py +0 -0
  59. /metaflow/{metadata → metadata_provider}/util.py +0 -0
  60. /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
  61. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/LICENSE +0 -0
  62. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/entry_points.txt +0 -0
  63. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/top_level.txt +0 -0
@@ -1,253 +1,94 @@
1
- import sys
2
- import json
3
- import tempfile
4
- from typing import Optional, ClassVar, List
5
-
6
- from metaflow.plugins.aws.step_functions.step_functions import StepFunctions
7
- from metaflow.runner.deployer import (
8
- DeployerImpl,
9
- DeployedFlow,
10
- TriggeredRun,
11
- get_lower_level_group,
12
- handle_timeout,
13
- )
14
-
15
-
16
- def terminate(instance: TriggeredRun, **kwargs):
17
- """
18
- Terminate the running workflow.
19
-
20
- Parameters
21
- ----------
22
- **kwargs : Any
23
- Additional arguments to pass to the terminate command.
24
-
25
- Returns
26
- -------
27
- bool
28
- True if the command was successful, False otherwise.
29
- """
30
- _, run_id = instance.pathspec.split("/")
1
+ from typing import Any, ClassVar, Dict, Optional, TYPE_CHECKING, Type
31
2
 
32
- # every subclass needs to have `self.deployer_kwargs`
33
- command = get_lower_level_group(
34
- instance.deployer.api,
35
- instance.deployer.top_level_kwargs,
36
- instance.deployer.TYPE,
37
- instance.deployer.deployer_kwargs,
38
- ).terminate(run_id=run_id, **kwargs)
3
+ from metaflow.runner.deployer_impl import DeployerImpl
39
4
 
40
- pid = instance.deployer.spm.run_command(
41
- [sys.executable, *command],
42
- env=instance.deployer.env_vars,
43
- cwd=instance.deployer.cwd,
44
- show_output=instance.deployer.show_output,
45
- )
46
-
47
- command_obj = instance.deployer.spm.get(pid)
48
- return command_obj.process.returncode == 0
49
-
50
-
51
- def production_token(instance: DeployedFlow):
52
- """
53
- Get the production token for the deployed flow.
54
-
55
- Returns
56
- -------
57
- str, optional
58
- The production token, None if it cannot be retrieved.
59
- """
60
- try:
61
- _, production_token = StepFunctions.get_existing_deployment(
62
- instance.deployer.name
63
- )
64
- return production_token
65
- except TypeError:
66
- return None
67
-
68
-
69
- def list_runs(instance: DeployedFlow, states: Optional[List[str]] = None):
70
- """
71
- List runs of the deployed flow.
72
-
73
- Parameters
74
- ----------
75
- states : Optional[List[str]], optional
76
- A list of states to filter the runs by. Allowed values are:
77
- RUNNING, SUCCEEDED, FAILED, TIMED_OUT, ABORTED.
78
- If not provided, all states will be considered.
79
-
80
- Returns
81
- -------
82
- List[TriggeredRun]
83
- A list of TriggeredRun objects representing the runs of the deployed flow.
84
-
85
- Raises
86
- ------
87
- ValueError
88
- If any of the provided states are invalid or if there are duplicate states.
89
- """
90
- VALID_STATES = {"RUNNING", "SUCCEEDED", "FAILED", "TIMED_OUT", "ABORTED"}
91
-
92
- if states is None:
93
- states = []
94
-
95
- unique_states = set(states)
96
- if not unique_states.issubset(VALID_STATES):
97
- invalid_states = unique_states - VALID_STATES
98
- raise ValueError(
99
- f"Invalid states found: {invalid_states}. Valid states are: {VALID_STATES}"
100
- )
101
-
102
- if len(states) != len(unique_states):
103
- raise ValueError("Duplicate states are not allowed")
104
-
105
- triggered_runs = []
106
- executions = StepFunctions.list(instance.deployer.name, states)
107
-
108
- for e in executions:
109
- run_id = "sfn-%s" % e["name"]
110
- tr = TriggeredRun(
111
- deployer=instance.deployer,
112
- content=json.dumps(
113
- {
114
- "metadata": instance.deployer.metadata,
115
- "pathspec": "/".join((instance.deployer.flow_name, run_id)),
116
- "name": run_id,
117
- }
118
- ),
119
- )
120
- tr._enrich_object({"terminate": terminate})
121
- triggered_runs.append(tr)
122
-
123
- return triggered_runs
124
-
125
-
126
- def delete(instance: DeployedFlow, **kwargs):
127
- """
128
- Delete the deployed flow.
129
-
130
- Parameters
131
- ----------
132
- **kwargs : Any
133
- Additional arguments to pass to the delete command.
134
-
135
- Returns
136
- -------
137
- bool
138
- True if the command was successful, False otherwise.
139
- """
140
- command = get_lower_level_group(
141
- instance.deployer.api,
142
- instance.deployer.top_level_kwargs,
143
- instance.deployer.TYPE,
144
- instance.deployer.deployer_kwargs,
145
- ).delete(**kwargs)
146
-
147
- pid = instance.deployer.spm.run_command(
148
- [sys.executable, *command],
149
- env=instance.deployer.env_vars,
150
- cwd=instance.deployer.cwd,
151
- show_output=instance.deployer.show_output,
152
- )
153
-
154
- command_obj = instance.deployer.spm.get(pid)
155
- return command_obj.process.returncode == 0
156
-
157
-
158
- def trigger(instance: DeployedFlow, **kwargs):
159
- """
160
- Trigger a new run for the deployed flow.
161
-
162
- Parameters
163
- ----------
164
- **kwargs : Any
165
- Additional arguments to pass to the trigger command, `Parameters` in particular
166
-
167
- Returns
168
- -------
169
- StepFunctionsTriggeredRun
170
- The triggered run instance.
171
-
172
- Raises
173
- ------
174
- Exception
175
- If there is an error during the trigger process.
176
- """
177
- with tempfile.TemporaryDirectory() as temp_dir:
178
- tfp_runner_attribute = tempfile.NamedTemporaryFile(dir=temp_dir, delete=False)
179
-
180
- # every subclass needs to have `self.deployer_kwargs`
181
- command = get_lower_level_group(
182
- instance.deployer.api,
183
- instance.deployer.top_level_kwargs,
184
- instance.deployer.TYPE,
185
- instance.deployer.deployer_kwargs,
186
- ).trigger(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
187
-
188
- pid = instance.deployer.spm.run_command(
189
- [sys.executable, *command],
190
- env=instance.deployer.env_vars,
191
- cwd=instance.deployer.cwd,
192
- show_output=instance.deployer.show_output,
193
- )
194
-
195
- command_obj = instance.deployer.spm.get(pid)
196
- content = handle_timeout(
197
- tfp_runner_attribute, command_obj, instance.deployer.file_read_timeout
198
- )
199
-
200
- if command_obj.process.returncode == 0:
201
- triggered_run = TriggeredRun(deployer=instance.deployer, content=content)
202
- triggered_run._enrich_object({"terminate": terminate})
203
- return triggered_run
204
-
205
- raise Exception(
206
- "Error triggering %s on %s for %s"
207
- % (instance.deployer.name, instance.deployer.TYPE, instance.deployer.flow_file)
208
- )
5
+ if TYPE_CHECKING:
6
+ import metaflow.plugins.aws.step_functions.step_functions_deployer_objects
209
7
 
210
8
 
211
9
  class StepFunctionsDeployer(DeployerImpl):
212
10
  """
213
11
  Deployer implementation for AWS Step Functions.
214
12
 
215
- Attributes
13
+ Parameters
216
14
  ----------
217
- TYPE : ClassVar[Optional[str]]
218
- The type of the deployer, which is "step-functions".
15
+ name : str, optional, default None
16
+ State Machine name. The flow name is used instead if this option is not specified.
219
17
  """
220
18
 
221
19
  TYPE: ClassVar[Optional[str]] = "step-functions"
222
20
 
223
- def __init__(self, deployer_kwargs, **kwargs):
21
+ def __init__(self, deployer_kwargs: Dict[str, str], **kwargs):
224
22
  """
225
23
  Initialize the StepFunctionsDeployer.
226
24
 
227
25
  Parameters
228
26
  ----------
229
- deployer_kwargs : dict
27
+ deployer_kwargs : Dict[str, str]
230
28
  The deployer-specific keyword arguments.
231
29
  **kwargs : Any
232
30
  Additional arguments to pass to the superclass constructor.
233
31
  """
234
- self.deployer_kwargs = deployer_kwargs
32
+ self._deployer_kwargs = deployer_kwargs
235
33
  super().__init__(**kwargs)
236
34
 
237
- def _enrich_deployed_flow(self, deployed_flow: DeployedFlow):
35
+ @property
36
+ def deployer_kwargs(self) -> Dict[str, Any]:
37
+ return self._deployer_kwargs
38
+
39
+ @staticmethod
40
+ def deployed_flow_type() -> (
41
+ Type[
42
+ "metaflow.plugins.aws.step_functions.step_functions_deployer_objects.StepFunctionsDeployedFlow"
43
+ ]
44
+ ):
45
+ from .step_functions_deployer_objects import StepFunctionsDeployedFlow
46
+
47
+ return StepFunctionsDeployedFlow
48
+
49
+ def create(
50
+ self, **kwargs
51
+ ) -> "metaflow.plugins.aws.step_functions.step_functions_deployer_objects.StepFunctionsDeployedFlow":
238
52
  """
239
- Enrich the DeployedFlow object with additional properties and methods.
53
+ Create a new AWS Step Functions State Machine deployment.
240
54
 
241
55
  Parameters
242
56
  ----------
243
- deployed_flow : DeployedFlow
244
- The deployed flow object to enrich.
57
+ authorize : str, optional, default None
58
+ Authorize using this production token. Required when re-deploying an existing flow
59
+ for the first time. The token is cached in METAFLOW_HOME.
60
+ generate_new_token : bool, optional, default False
61
+ Generate a new production token for this flow. Moves the production flow to a new namespace.
62
+ given_token : str, optional, default None
63
+ Use the given production token for this flow. Moves the production flow to the given namespace.
64
+ tags : List[str], optional, default None
65
+ Annotate all objects produced by AWS Step Functions runs with these tags.
66
+ user_namespace : str, optional, default None
67
+ Change the namespace from the default (production token) to the given tag.
68
+ only_json : bool, optional, default False
69
+ Only print out JSON sent to AWS Step Functions without deploying anything.
70
+ max_workers : int, optional, default 100
71
+ Maximum number of parallel processes.
72
+ workflow_timeout : int, optional, default None
73
+ Workflow timeout in seconds.
74
+ log_execution_history : bool, optional, default False
75
+ Log AWS Step Functions execution history to AWS CloudWatch Logs log group.
76
+ use_distributed_map : bool, optional, default False
77
+ Use AWS Step Functions Distributed Map instead of Inline Map for defining foreach
78
+ tasks in Amazon State Language.
79
+ deployer_attribute_file : str, optional, default None
80
+ Write the workflow name to the specified file. Used internally for Metaflow's Deployer API.
81
+
82
+ Returns
83
+ -------
84
+ StepFunctionsDeployedFlow
85
+ The Flow deployed to AWS Step Functions.
245
86
  """
246
- deployed_flow._enrich_object(
247
- {
248
- "production_token": property(production_token),
249
- "trigger": trigger,
250
- "delete": delete,
251
- "list_runs": list_runs,
252
- }
253
- )
87
+ from .step_functions_deployer_objects import StepFunctionsDeployedFlow
88
+
89
+ return self._create(StepFunctionsDeployedFlow, **kwargs)
90
+
91
+
92
+ _addl_stubgen_modules = [
93
+ "metaflow.plugins.aws.step_functions.step_functions_deployer_objects"
94
+ ]
@@ -0,0 +1,236 @@
1
+ import sys
2
+ import json
3
+ import tempfile
4
+ from typing import ClassVar, Optional, List
5
+
6
+ from metaflow.plugins.aws.step_functions.step_functions import StepFunctions
7
+ from metaflow.runner.deployer import DeployedFlow, TriggeredRun
8
+
9
+ from metaflow.runner.utils import get_lower_level_group, handle_timeout
10
+
11
+
12
+ class StepFunctionsTriggeredRun(TriggeredRun):
13
+ """
14
+ A class representing a triggered AWS Step Functions state machine execution.
15
+ """
16
+
17
+ def terminate(self, **kwargs) -> bool:
18
+ """
19
+ Terminate the running state machine execution.
20
+
21
+ Parameters
22
+ ----------
23
+ authorize : str, optional, default None
24
+ Authorize the termination with a production token.
25
+
26
+ Returns
27
+ -------
28
+ bool
29
+ True if the command was successful, False otherwise.
30
+ """
31
+ _, run_id = self.pathspec.split("/")
32
+
33
+ # every subclass needs to have `self.deployer_kwargs`
34
+ command = get_lower_level_group(
35
+ self.deployer.api,
36
+ self.deployer.top_level_kwargs,
37
+ self.deployer.TYPE,
38
+ self.deployer.deployer_kwargs,
39
+ ).terminate(run_id=run_id, **kwargs)
40
+
41
+ pid = self.deployer.spm.run_command(
42
+ [sys.executable, *command],
43
+ env=self.deployer.env_vars,
44
+ cwd=self.deployer.cwd,
45
+ show_output=self.deployer.show_output,
46
+ )
47
+
48
+ command_obj = self.deployer.spm.get(pid)
49
+ return command_obj.process.returncode == 0
50
+
51
+
52
+ class StepFunctionsDeployedFlow(DeployedFlow):
53
+ """
54
+ A class representing a deployed AWS Step Functions state machine.
55
+ """
56
+
57
+ TYPE: ClassVar[Optional[str]] = "step-functions"
58
+
59
+ @classmethod
60
+ def from_deployment(cls, identifier: str, metadata: Optional[str] = None):
61
+ """
62
+ This method is not currently implemented for Step Functions.
63
+
64
+ Raises
65
+ ------
66
+ NotImplementedError
67
+ This method is not implemented for Step Functions.
68
+ """
69
+ raise NotImplementedError(
70
+ "from_deployment is not implemented for StepFunctions"
71
+ )
72
+
73
+ @property
74
+ def production_token(self: DeployedFlow) -> Optional[str]:
75
+ """
76
+ Get the production token for the deployed flow.
77
+
78
+ Returns
79
+ -------
80
+ str, optional
81
+ The production token, None if it cannot be retrieved.
82
+ """
83
+ try:
84
+ _, production_token = StepFunctions.get_existing_deployment(
85
+ self.deployer.name
86
+ )
87
+ return production_token
88
+ except TypeError:
89
+ return None
90
+
91
+ def list_runs(
92
+ self, states: Optional[List[str]] = None
93
+ ) -> List[StepFunctionsTriggeredRun]:
94
+ """
95
+ List runs of the deployed flow.
96
+
97
+ Parameters
98
+ ----------
99
+ states : List[str], optional, default None
100
+ A list of states to filter the runs by. Allowed values are:
101
+ RUNNING, SUCCEEDED, FAILED, TIMED_OUT, ABORTED.
102
+ If not provided, all states will be considered.
103
+
104
+ Returns
105
+ -------
106
+ List[StepFunctionsTriggeredRun]
107
+ A list of TriggeredRun objects representing the runs of the deployed flow.
108
+
109
+ Raises
110
+ ------
111
+ ValueError
112
+ If any of the provided states are invalid or if there are duplicate states.
113
+ """
114
+ VALID_STATES = {"RUNNING", "SUCCEEDED", "FAILED", "TIMED_OUT", "ABORTED"}
115
+
116
+ if states is None:
117
+ states = []
118
+
119
+ unique_states = set(states)
120
+ if not unique_states.issubset(VALID_STATES):
121
+ invalid_states = unique_states - VALID_STATES
122
+ raise ValueError(
123
+ f"Invalid states found: {invalid_states}. Valid states are: {VALID_STATES}"
124
+ )
125
+
126
+ if len(states) != len(unique_states):
127
+ raise ValueError("Duplicate states are not allowed")
128
+
129
+ triggered_runs = []
130
+ executions = StepFunctions.list(self.deployer.name, states)
131
+
132
+ for e in executions:
133
+ run_id = "sfn-%s" % e["name"]
134
+ tr = StepFunctionsTriggeredRun(
135
+ deployer=self.deployer,
136
+ content=json.dumps(
137
+ {
138
+ "metadata": self.deployer.metadata,
139
+ "pathspec": "/".join((self.deployer.flow_name, run_id)),
140
+ "name": run_id,
141
+ }
142
+ ),
143
+ )
144
+ triggered_runs.append(tr)
145
+
146
+ return triggered_runs
147
+
148
+ def delete(self, **kwargs) -> bool:
149
+ """
150
+ Delete the deployed state machine.
151
+
152
+ Parameters
153
+ ----------
154
+ authorize : str, optional, default None
155
+ Authorize the deletion with a production token.
156
+
157
+ Returns
158
+ -------
159
+ bool
160
+ True if the command was successful, False otherwise.
161
+ """
162
+ command = get_lower_level_group(
163
+ self.deployer.api,
164
+ self.deployer.top_level_kwargs,
165
+ self.deployer.TYPE,
166
+ self.deployer.deployer_kwargs,
167
+ ).delete(**kwargs)
168
+
169
+ pid = self.deployer.spm.run_command(
170
+ [sys.executable, *command],
171
+ env=self.deployer.env_vars,
172
+ cwd=self.deployer.cwd,
173
+ show_output=self.deployer.show_output,
174
+ )
175
+
176
+ command_obj = self.deployer.spm.get(pid)
177
+ return command_obj.process.returncode == 0
178
+
179
+ def trigger(self, **kwargs) -> StepFunctionsTriggeredRun:
180
+ """
181
+ Trigger a new run for the deployed flow.
182
+
183
+ Parameters
184
+ ----------
185
+ **kwargs : Any
186
+ Additional arguments to pass to the trigger command,
187
+ `Parameters` in particular
188
+
189
+ Returns
190
+ -------
191
+ StepFunctionsTriggeredRun
192
+ The triggered run instance.
193
+
194
+ Raises
195
+ ------
196
+ Exception
197
+ If there is an error during the trigger process.
198
+ """
199
+ with tempfile.TemporaryDirectory() as temp_dir:
200
+ tfp_runner_attribute = tempfile.NamedTemporaryFile(
201
+ dir=temp_dir, delete=False
202
+ )
203
+
204
+ # every subclass needs to have `self.deployer_kwargs`
205
+ command = get_lower_level_group(
206
+ self.deployer.api,
207
+ self.deployer.top_level_kwargs,
208
+ self.deployer.TYPE,
209
+ self.deployer.deployer_kwargs,
210
+ ).trigger(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
211
+
212
+ pid = self.deployer.spm.run_command(
213
+ [sys.executable, *command],
214
+ env=self.deployer.env_vars,
215
+ cwd=self.deployer.cwd,
216
+ show_output=self.deployer.show_output,
217
+ )
218
+
219
+ command_obj = self.deployer.spm.get(pid)
220
+ content = handle_timeout(
221
+ tfp_runner_attribute, command_obj, self.deployer.file_read_timeout
222
+ )
223
+
224
+ if command_obj.process.returncode == 0:
225
+ return StepFunctionsTriggeredRun(
226
+ deployer=self.deployer, content=content
227
+ )
228
+
229
+ raise Exception(
230
+ "Error triggering %s on %s for %s"
231
+ % (
232
+ self.deployer.name,
233
+ self.deployer.TYPE,
234
+ self.deployer.flow_file,
235
+ )
236
+ )
@@ -8,6 +8,8 @@ from metaflow.exception import MetaflowException, MetaflowInternalError
8
8
 
9
9
 
10
10
  class Azure(object):
11
+ TYPE = "azure"
12
+
11
13
  @classmethod
12
14
  def get_root_from_config(cls, echo, create_on_absent=True):
13
15
  from metaflow.metaflow_config import DATATOOLS_AZUREROOT
@@ -1,5 +1,6 @@
1
1
  from metaflow.client import Task
2
- from metaflow import JSONType, namespace
2
+ from metaflow.parameters import JSONTypeClass
3
+ from metaflow import namespace
3
4
  from metaflow.util import resolve_identity
4
5
  from metaflow.exception import (
5
6
  CommandException,
@@ -551,7 +552,7 @@ def update_card(mf_card, mode, task, data, timeout_value=None):
551
552
  "--options",
552
553
  default=None,
553
554
  show_default=True,
554
- type=JSONType,
555
+ type=JSONTypeClass(),
555
556
  help="arguments of the card being created.",
556
557
  )
557
558
  @click.option(
@@ -712,15 +712,15 @@ class ProgressBar(UserComponent):
712
712
 
713
713
  Parameters
714
714
  ----------
715
- max : int
715
+ max : int, default 100
716
716
  The maximum value of the progress bar.
717
- label : str, optional
717
+ label : str, optional, default None
718
718
  Optional label for the progress bar.
719
- value : int, optional
719
+ value : int, default 0
720
720
  Optional initial value of the progress bar.
721
- unit : str, optional
721
+ unit : str, optional, default None
722
722
  Optional unit for the progress bar.
723
- metadata : str, optional
723
+ metadata : str, optional, default None
724
724
  Optional additional information to show on the progress bar.
725
725
  """
726
726
 
@@ -731,10 +731,10 @@ class ProgressBar(UserComponent):
731
731
  def __init__(
732
732
  self,
733
733
  max: int = 100,
734
- label: str = None,
734
+ label: Optional[str] = None,
735
735
  value: int = 0,
736
- unit: str = None,
737
- metadata: str = None,
736
+ unit: Optional[str] = None,
737
+ metadata: Optional[str] = None,
738
738
  ):
739
739
  self._label = label
740
740
  self._max = max
@@ -742,7 +742,7 @@ class ProgressBar(UserComponent):
742
742
  self._unit = unit
743
743
  self._metadata = metadata
744
744
 
745
- def update(self, new_value: int, metadata: str = None):
745
+ def update(self, new_value: int, metadata: Optional[str] = None):
746
746
  self._value = new_value
747
747
  if metadata is not None:
748
748
  self._metadata = metadata
@@ -20,14 +20,9 @@ except ImportError:
20
20
  from .card_client import CardContainer
21
21
  from .exception import CardNotPresentException
22
22
  from .card_resolver import resolve_paths_from_task
23
- from metaflow.metaflow_config import DATASTORE_LOCAL_DIR
24
23
  from metaflow import namespace
25
- from metaflow.exception import (
26
- CommandException,
27
- MetaflowNotFound,
28
- MetaflowNamespaceMismatch,
29
- )
30
-
24
+ from metaflow.exception import MetaflowNotFound
25
+ from metaflow.plugins.datastores.local_storage import LocalStorage
31
26
 
32
27
  VIEWER_PATH = os.path.join(
33
28
  os.path.dirname(os.path.abspath(__file__)), "card_viewer", "viewer.html"
@@ -50,18 +45,48 @@ class RunWatcher(Thread):
50
45
  def __init__(self, flow_name, connection: Connection):
51
46
  super().__init__()
52
47
 
53
- self._watch_file = os.path.join(
54
- os.getcwd(), DATASTORE_LOCAL_DIR, flow_name, "latest_run"
55
- )
56
- self._current_run_id = self.get_run_id()
57
48
  self.daemon = True
58
49
  self._connection = connection
50
+ self._flow_name = flow_name
51
+
52
+ self._watch_file = self._initialize_watch_file()
53
+ if self._watch_file is None:
54
+ _ClickLogger(
55
+ "Warning: Could not initialize watch file location.", fg="yellow"
56
+ )
57
+
58
+ self._current_run_id = self.get_run_id()
59
+
60
+ def _initialize_watch_file(self):
61
+ local_root = LocalStorage.datastore_root
62
+ if local_root is None:
63
+ local_root = LocalStorage.get_datastore_root_from_config(
64
+ lambda _: None, create_on_absent=False
65
+ )
66
+
67
+ return (
68
+ os.path.join(local_root, self._flow_name, "latest_run")
69
+ if local_root
70
+ else None
71
+ )
59
72
 
60
73
  def get_run_id(self):
61
- if not os.path.exists(self._watch_file):
74
+ # Try to reinitialize watch file if needed
75
+ if not self._watch_file:
76
+ self._watch_file = self._initialize_watch_file()
77
+
78
+ # Early return if watch file is still None or doesn't exist
79
+ if not (self._watch_file and os.path.exists(self._watch_file)):
80
+ return None
81
+
82
+ try:
83
+ with open(self._watch_file, "r") as f:
84
+ return f.read().strip()
85
+ except (IOError, OSError) as e:
86
+ _ClickLogger(
87
+ "Warning: Could not read run ID from watch file: %s" % e, fg="yellow"
88
+ )
62
89
  return None
63
- with open(self._watch_file, "r") as f:
64
- return f.read().strip()
65
90
 
66
91
  def watch(self):
67
92
  while True: