metaflow 2.15.14__py2.py3-none-any.whl → 2.15.16__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 (44) hide show
  1. metaflow/__init__.py +2 -2
  2. metaflow/_vendor/click/core.py +4 -3
  3. metaflow/cmd/develop/stub_generator.py +30 -16
  4. metaflow/cmd/develop/stubs.py +9 -27
  5. metaflow/datastore/task_datastore.py +3 -3
  6. metaflow/decorators.py +3 -3
  7. metaflow/extension_support/__init__.py +25 -42
  8. metaflow/parameters.py +2 -2
  9. metaflow/plugins/argo/argo_workflows_cli.py +4 -4
  10. metaflow/plugins/argo/argo_workflows_deployer_objects.py +6 -49
  11. metaflow/plugins/aws/aws_client.py +6 -0
  12. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  13. metaflow/plugins/cards/card_modules/test_cards.py +6 -6
  14. metaflow/plugins/cards/component_serializer.py +1 -8
  15. metaflow/plugins/datatools/s3/s3op.py +1 -1
  16. metaflow/plugins/metadata_providers/service.py +12 -8
  17. metaflow/plugins/package_cli.py +12 -2
  18. metaflow/plugins/pypi/bootstrap.py +2 -2
  19. metaflow/plugins/uv/bootstrap.py +18 -1
  20. metaflow/plugins/uv/uv_environment.py +1 -1
  21. metaflow/runner/click_api.py +16 -9
  22. metaflow/runner/deployer.py +49 -0
  23. metaflow/runner/deployer_impl.py +17 -5
  24. metaflow/runner/metaflow_runner.py +40 -13
  25. metaflow/runner/subprocess_manager.py +1 -1
  26. metaflow/runner/utils.py +8 -0
  27. metaflow/user_configs/config_options.py +6 -6
  28. metaflow/user_configs/config_parameters.py +211 -45
  29. metaflow/util.py +2 -5
  30. metaflow/vendor.py +0 -1
  31. metaflow/version.py +1 -1
  32. {metaflow-2.15.14.dist-info → metaflow-2.15.16.dist-info}/METADATA +2 -2
  33. {metaflow-2.15.14.dist-info → metaflow-2.15.16.dist-info}/RECORD +40 -44
  34. {metaflow-2.15.14.dist-info → metaflow-2.15.16.dist-info}/WHEEL +1 -1
  35. metaflow/_vendor/v3_5/__init__.py +0 -1
  36. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  37. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  38. metaflow/_vendor/v3_5/zipp.py +0 -329
  39. {metaflow-2.15.14.data → metaflow-2.15.16.data}/data/share/metaflow/devtools/Makefile +0 -0
  40. {metaflow-2.15.14.data → metaflow-2.15.16.data}/data/share/metaflow/devtools/Tiltfile +0 -0
  41. {metaflow-2.15.14.data → metaflow-2.15.16.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  42. {metaflow-2.15.14.dist-info → metaflow-2.15.16.dist-info}/entry_points.txt +0 -0
  43. {metaflow-2.15.14.dist-info → metaflow-2.15.16.dist-info}/licenses/LICENSE +0 -0
  44. {metaflow-2.15.14.dist-info → metaflow-2.15.16.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ import sys
4
4
  import time
5
5
 
6
6
  from metaflow.util import which
7
+ from metaflow.info_file import read_info_file
7
8
  from metaflow.metaflow_config import get_pinned_conda_libs
8
9
  from urllib.request import Request, urlopen
9
10
  from urllib.error import URLError
@@ -78,11 +79,27 @@ if __name__ == "__main__":
78
79
  # return only dependency names instead of pinned versions
79
80
  return pinned.keys()
80
81
 
82
+ def skip_metaflow_dependencies():
83
+ skip_pkgs = ["metaflow"]
84
+ info = read_info_file()
85
+ if info is not None:
86
+ try:
87
+ skip_pkgs.extend([ext_name for ext_name in info["ext_info"][0].keys()])
88
+ except Exception:
89
+ print(
90
+ "Failed to read INFO. Metaflow-related packages might get installed during runtime."
91
+ )
92
+
93
+ return skip_pkgs
94
+
81
95
  def sync_uv_project(datastore_type):
82
96
  print("Syncing uv project...")
83
97
  dependencies = " ".join(get_dependencies(datastore_type))
98
+ skip_pkgs = " ".join(
99
+ [f"--no-install-package {dep}" for dep in skip_metaflow_dependencies()]
100
+ )
84
101
  cmd = f"""set -e;
85
- uv sync --frozen --no-install-package metaflow;
102
+ uv sync --frozen {skip_pkgs};
86
103
  uv pip install {dependencies} --strict
87
104
  """
88
105
  run_cmd(cmd)
@@ -22,7 +22,7 @@ class UVEnvironment(MetaflowEnvironment):
22
22
  self.logger("Bootstrapping uv...")
23
23
 
24
24
  def executable(self, step_name, default=None):
25
- return "uv run python"
25
+ return "uv run --no-sync python"
26
26
 
27
27
  def add_to_package(self):
28
28
  # NOTE: We treat uv.lock and pyproject.toml as regular project assets and ship these along user code as part of the code package
@@ -467,9 +467,14 @@ class MetaflowAPI(object):
467
467
  config_file = defaults.get("config")
468
468
 
469
469
  if config_file:
470
- config_file = map(
471
- lambda x: (x[0], ConvertPath.convert_value(x[1], is_default)),
472
- config_file,
470
+ config_file = dict(
471
+ map(
472
+ lambda x: (
473
+ x[0],
474
+ ConvertPath.convert_value(x[1], is_default),
475
+ ),
476
+ config_file,
477
+ )
473
478
  )
474
479
 
475
480
  is_default = False
@@ -479,12 +484,14 @@ class MetaflowAPI(object):
479
484
  config_value = defaults.get("config_value")
480
485
 
481
486
  if config_value:
482
- config_value = map(
483
- lambda x: (
484
- x[0],
485
- ConvertDictOrStr.convert_value(x[1], is_default),
486
- ),
487
- config_value,
487
+ config_value = dict(
488
+ map(
489
+ lambda x: (
490
+ x[0],
491
+ ConvertDictOrStr.convert_value(x[1], is_default),
492
+ ),
493
+ config_value,
494
+ )
488
495
  )
489
496
 
490
497
  if (config_file is None) ^ (config_value is None):
@@ -7,6 +7,55 @@ from typing import ClassVar, Dict, Optional, TYPE_CHECKING
7
7
  from metaflow.exception import MetaflowNotFound
8
8
  from metaflow.metaflow_config import DEFAULT_FROM_DEPLOYMENT_IMPL
9
9
 
10
+
11
+ def generate_fake_flow_file_contents(
12
+ flow_name: str, param_info: dict, project_name: Optional[str] = None
13
+ ):
14
+ params_code = ""
15
+ for _, param_details in param_info.items():
16
+ param_python_var_name = param_details["python_var_name"]
17
+ param_name = param_details["name"]
18
+ param_type = param_details["type"]
19
+ param_help = param_details["description"]
20
+ param_required = param_details["is_required"]
21
+
22
+ if param_type == "JSON":
23
+ params_code += (
24
+ f" {param_python_var_name} = Parameter('{param_name}', "
25
+ f"type=JSONType, help='''{param_help}''', required={param_required})\n"
26
+ )
27
+ elif param_type == "FilePath":
28
+ is_text = param_details.get("is_text", True)
29
+ encoding = param_details.get("encoding", "utf-8")
30
+ params_code += (
31
+ f" {param_python_var_name} = IncludeFile('{param_name}', "
32
+ f"is_text={is_text}, encoding='{encoding}', help='''{param_help}''', "
33
+ f"required={param_required})\n"
34
+ )
35
+ else:
36
+ params_code += (
37
+ f" {param_python_var_name} = Parameter('{param_name}', "
38
+ f"type={param_type}, help='''{param_help}''', required={param_required})\n"
39
+ )
40
+
41
+ project_decorator = f"@project(name='{project_name}')\n" if project_name else ""
42
+
43
+ contents = f"""\
44
+ from metaflow import FlowSpec, Parameter, IncludeFile, JSONType, step, project
45
+ {project_decorator}class {flow_name}(FlowSpec):
46
+ {params_code}
47
+ @step
48
+ def start(self):
49
+ self.next(self.end)
50
+ @step
51
+ def end(self):
52
+ pass
53
+ if __name__ == '__main__':
54
+ {flow_name}()
55
+ """
56
+ return contents
57
+
58
+
10
59
  if TYPE_CHECKING:
11
60
  import metaflow
12
61
  import metaflow.runner.deployer_impl
@@ -5,8 +5,10 @@ import sys
5
5
 
6
6
  from typing import Any, ClassVar, Dict, Optional, TYPE_CHECKING, Type
7
7
 
8
+ from metaflow.metaflow_config import CLICK_API_PROCESS_CONFIG
9
+
8
10
  from .subprocess_manager import SubprocessManager
9
- from .utils import get_lower_level_group, handle_timeout, temporary_fifo
11
+ from .utils import get_lower_level_group, handle_timeout, temporary_fifo, with_dir
10
12
 
11
13
  if TYPE_CHECKING:
12
14
  import metaflow.runner.deployer
@@ -88,7 +90,7 @@ class DeployerImpl(object):
88
90
  self.show_output = show_output
89
91
  self.profile = profile
90
92
  self.env = env
91
- self.cwd = cwd
93
+ self.cwd = cwd or os.getcwd()
92
94
  self.file_read_timeout = file_read_timeout
93
95
 
94
96
  self.env_vars = os.environ.copy()
@@ -140,9 +142,19 @@ class DeployerImpl(object):
140
142
  ) -> "metaflow.runner.deployer.DeployedFlow":
141
143
  with temporary_fifo() as (attribute_file_path, attribute_file_fd):
142
144
  # every subclass needs to have `self.deployer_kwargs`
143
- command = get_lower_level_group(
144
- self.api, self.top_level_kwargs, self.TYPE, self.deployer_kwargs
145
- ).create(deployer_attribute_file=attribute_file_path, **kwargs)
145
+ # TODO: Get rid of CLICK_API_PROCESS_CONFIG in the near future
146
+ if CLICK_API_PROCESS_CONFIG:
147
+ # We need to run this in the cwd because configs depend on files
148
+ # that may be located in paths relative to the directory the user
149
+ # wants to run in
150
+ with with_dir(self.cwd):
151
+ command = get_lower_level_group(
152
+ self.api, self.top_level_kwargs, self.TYPE, self.deployer_kwargs
153
+ ).create(deployer_attribute_file=attribute_file_path, **kwargs)
154
+ else:
155
+ command = get_lower_level_group(
156
+ self.api, self.top_level_kwargs, self.TYPE, self.deployer_kwargs
157
+ ).create(deployer_attribute_file=attribute_file_path, **kwargs)
146
158
 
147
159
  pid = self.spm.run_command(
148
160
  [sys.executable, *command],
@@ -7,12 +7,15 @@ from typing import Dict, Iterator, Optional, Tuple
7
7
 
8
8
  from metaflow import Run
9
9
 
10
+ from metaflow.metaflow_config import CLICK_API_PROCESS_CONFIG
11
+
10
12
  from metaflow.plugins import get_runner_cli
11
13
 
12
14
  from .utils import (
13
15
  temporary_fifo,
14
16
  handle_timeout,
15
17
  async_handle_timeout,
18
+ with_dir,
16
19
  )
17
20
  from .subprocess_manager import CommandManager, SubprocessManager
18
21
 
@@ -299,7 +302,7 @@ class Runner(metaclass=RunnerMeta):
299
302
  if profile:
300
303
  self.env_vars["METAFLOW_PROFILE"] = profile
301
304
 
302
- self.cwd = cwd
305
+ self.cwd = cwd or os.getcwd()
303
306
  self.file_read_timeout = file_read_timeout
304
307
  self.spm = SubprocessManager()
305
308
  self.top_level_kwargs = kwargs
@@ -359,9 +362,15 @@ class Runner(metaclass=RunnerMeta):
359
362
  ExecutingRun containing the results of the run.
360
363
  """
361
364
  with temporary_fifo() as (attribute_file_path, attribute_file_fd):
362
- command = self.api(**self.top_level_kwargs).run(
363
- runner_attribute_file=attribute_file_path, **kwargs
364
- )
365
+ if CLICK_API_PROCESS_CONFIG:
366
+ with with_dir(self.cwd):
367
+ command = self.api(**self.top_level_kwargs).run(
368
+ runner_attribute_file=attribute_file_path, **kwargs
369
+ )
370
+ else:
371
+ command = self.api(**self.top_level_kwargs).run(
372
+ runner_attribute_file=attribute_file_path, **kwargs
373
+ )
365
374
 
366
375
  pid = self.spm.run_command(
367
376
  [sys.executable, *command],
@@ -390,9 +399,15 @@ class Runner(metaclass=RunnerMeta):
390
399
  ExecutingRun containing the results of the resumed run.
391
400
  """
392
401
  with temporary_fifo() as (attribute_file_path, attribute_file_fd):
393
- command = self.api(**self.top_level_kwargs).resume(
394
- runner_attribute_file=attribute_file_path, **kwargs
395
- )
402
+ if CLICK_API_PROCESS_CONFIG:
403
+ with with_dir(self.cwd):
404
+ command = self.api(**self.top_level_kwargs).resume(
405
+ runner_attribute_file=attribute_file_path, **kwargs
406
+ )
407
+ else:
408
+ command = self.api(**self.top_level_kwargs).resume(
409
+ runner_attribute_file=attribute_file_path, **kwargs
410
+ )
396
411
 
397
412
  pid = self.spm.run_command(
398
413
  [sys.executable, *command],
@@ -423,9 +438,15 @@ class Runner(metaclass=RunnerMeta):
423
438
  ExecutingRun representing the run that was started.
424
439
  """
425
440
  with temporary_fifo() as (attribute_file_path, attribute_file_fd):
426
- command = self.api(**self.top_level_kwargs).run(
427
- runner_attribute_file=attribute_file_path, **kwargs
428
- )
441
+ if CLICK_API_PROCESS_CONFIG:
442
+ with with_dir(self.cwd):
443
+ command = self.api(**self.top_level_kwargs).run(
444
+ runner_attribute_file=attribute_file_path, **kwargs
445
+ )
446
+ else:
447
+ command = self.api(**self.top_level_kwargs).run(
448
+ runner_attribute_file=attribute_file_path, **kwargs
449
+ )
429
450
 
430
451
  pid = await self.spm.async_run_command(
431
452
  [sys.executable, *command],
@@ -455,9 +476,15 @@ class Runner(metaclass=RunnerMeta):
455
476
  ExecutingRun representing the resumed run that was started.
456
477
  """
457
478
  with temporary_fifo() as (attribute_file_path, attribute_file_fd):
458
- command = self.api(**self.top_level_kwargs).resume(
459
- runner_attribute_file=attribute_file_path, **kwargs
460
- )
479
+ if CLICK_API_PROCESS_CONFIG:
480
+ with with_dir(self.cwd):
481
+ command = self.api(**self.top_level_kwargs).resume(
482
+ runner_attribute_file=attribute_file_path, **kwargs
483
+ )
484
+ else:
485
+ command = self.api(**self.top_level_kwargs).resume(
486
+ runner_attribute_file=attribute_file_path, **kwargs
487
+ )
461
488
 
462
489
  pid = await self.spm.async_run_command(
463
490
  [sys.executable, *command],
@@ -237,7 +237,7 @@ class CommandManager(object):
237
237
  self.command = command
238
238
 
239
239
  self.env = env if env is not None else os.environ.copy()
240
- self.cwd = cwd if cwd is not None else os.getcwd()
240
+ self.cwd = cwd or os.getcwd()
241
241
 
242
242
  self.process = None
243
243
  self.stdout_thread = None
metaflow/runner/utils.py CHANGED
@@ -322,3 +322,11 @@ def get_lower_level_group(
322
322
  raise ValueError(f"Sub-command '{sub_command}' not found in API '{api.name}'")
323
323
 
324
324
  return sub_command_obj(**sub_command_kwargs)
325
+
326
+
327
+ @contextmanager
328
+ def with_dir(new_dir):
329
+ current_dir = os.getcwd()
330
+ os.chdir(new_dir)
331
+ yield new_dir
332
+ os.chdir(current_dir)
@@ -221,13 +221,13 @@ class ConfigInput:
221
221
  if param_name == "config_value":
222
222
  self._value_values = {
223
223
  k.lower(): v
224
- for k, v in param_value
224
+ for k, v in param_value.items()
225
225
  if v is not None and not v.startswith(_CONVERTED_DEFAULT)
226
226
  }
227
227
  else:
228
228
  self._path_values = {
229
229
  k.lower(): v
230
- for k, v in param_value
230
+ for k, v in param_value.items()
231
231
  if v is not None and not v.startswith(_CONVERTED_DEFAULT)
232
232
  }
233
233
  if do_return:
@@ -329,12 +329,12 @@ class ConfigInput:
329
329
  to_return[name] = None
330
330
  flow_cls._flow_state[_FlowState.CONFIGS][name] = None
331
331
  continue
332
- if val.startswith(_CONVERTED_DEFAULT_NO_FILE):
333
- no_default_file.append(name)
334
- continue
335
332
  if val.startswith(_CONVERTED_NO_FILE):
336
333
  no_file.append(name)
337
334
  continue
335
+ if val.startswith(_CONVERTED_DEFAULT_NO_FILE):
336
+ no_default_file.append(name)
337
+ continue
338
338
 
339
339
  val = val[len(_CONVERT_PREFIX) :] # Remove the _CONVERT_PREFIX
340
340
  if val.startswith(_DEFAULT_PREFIX): # Remove the _DEFAULT_PREFIX if needed
@@ -398,7 +398,7 @@ class ConfigInput:
398
398
  return self.process_configs(
399
399
  ctx.obj.flow.name,
400
400
  param.name,
401
- value,
401
+ dict(value),
402
402
  ctx.params["quiet"],
403
403
  ctx.params["datastore"],
404
404
  click_obj=ctx.obj,