ob-metaflow 2.15.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__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 (169) hide show
  1. metaflow/__init__.py +10 -3
  2. metaflow/_vendor/imghdr/__init__.py +186 -0
  3. metaflow/_vendor/yaml/__init__.py +427 -0
  4. metaflow/_vendor/yaml/composer.py +139 -0
  5. metaflow/_vendor/yaml/constructor.py +748 -0
  6. metaflow/_vendor/yaml/cyaml.py +101 -0
  7. metaflow/_vendor/yaml/dumper.py +62 -0
  8. metaflow/_vendor/yaml/emitter.py +1137 -0
  9. metaflow/_vendor/yaml/error.py +75 -0
  10. metaflow/_vendor/yaml/events.py +86 -0
  11. metaflow/_vendor/yaml/loader.py +63 -0
  12. metaflow/_vendor/yaml/nodes.py +49 -0
  13. metaflow/_vendor/yaml/parser.py +589 -0
  14. metaflow/_vendor/yaml/reader.py +185 -0
  15. metaflow/_vendor/yaml/representer.py +389 -0
  16. metaflow/_vendor/yaml/resolver.py +227 -0
  17. metaflow/_vendor/yaml/scanner.py +1435 -0
  18. metaflow/_vendor/yaml/serializer.py +111 -0
  19. metaflow/_vendor/yaml/tokens.py +104 -0
  20. metaflow/cards.py +4 -0
  21. metaflow/cli.py +125 -21
  22. metaflow/cli_components/init_cmd.py +1 -0
  23. metaflow/cli_components/run_cmds.py +204 -40
  24. metaflow/cli_components/step_cmd.py +160 -4
  25. metaflow/client/__init__.py +1 -0
  26. metaflow/client/core.py +198 -130
  27. metaflow/client/filecache.py +59 -32
  28. metaflow/cmd/code/__init__.py +2 -1
  29. metaflow/cmd/develop/stub_generator.py +49 -18
  30. metaflow/cmd/develop/stubs.py +9 -27
  31. metaflow/cmd/make_wrapper.py +30 -0
  32. metaflow/datastore/__init__.py +1 -0
  33. metaflow/datastore/content_addressed_store.py +40 -9
  34. metaflow/datastore/datastore_set.py +10 -1
  35. metaflow/datastore/flow_datastore.py +124 -4
  36. metaflow/datastore/spin_datastore.py +91 -0
  37. metaflow/datastore/task_datastore.py +92 -6
  38. metaflow/debug.py +5 -0
  39. metaflow/decorators.py +331 -82
  40. metaflow/extension_support/__init__.py +414 -356
  41. metaflow/extension_support/_empty_file.py +2 -2
  42. metaflow/flowspec.py +322 -82
  43. metaflow/graph.py +178 -15
  44. metaflow/includefile.py +25 -3
  45. metaflow/lint.py +94 -3
  46. metaflow/meta_files.py +13 -0
  47. metaflow/metadata_provider/metadata.py +13 -2
  48. metaflow/metaflow_config.py +66 -4
  49. metaflow/metaflow_environment.py +91 -25
  50. metaflow/metaflow_profile.py +18 -0
  51. metaflow/metaflow_version.py +16 -1
  52. metaflow/package/__init__.py +673 -0
  53. metaflow/packaging_sys/__init__.py +880 -0
  54. metaflow/packaging_sys/backend.py +128 -0
  55. metaflow/packaging_sys/distribution_support.py +153 -0
  56. metaflow/packaging_sys/tar_backend.py +99 -0
  57. metaflow/packaging_sys/utils.py +54 -0
  58. metaflow/packaging_sys/v1.py +527 -0
  59. metaflow/parameters.py +6 -2
  60. metaflow/plugins/__init__.py +6 -0
  61. metaflow/plugins/airflow/airflow.py +11 -1
  62. metaflow/plugins/airflow/airflow_cli.py +16 -5
  63. metaflow/plugins/argo/argo_client.py +42 -20
  64. metaflow/plugins/argo/argo_events.py +6 -6
  65. metaflow/plugins/argo/argo_workflows.py +1023 -344
  66. metaflow/plugins/argo/argo_workflows_cli.py +396 -94
  67. metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
  68. metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
  69. metaflow/plugins/argo/capture_error.py +5 -2
  70. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  71. metaflow/plugins/argo/exit_hooks.py +209 -0
  72. metaflow/plugins/argo/param_val.py +19 -0
  73. metaflow/plugins/aws/aws_client.py +6 -0
  74. metaflow/plugins/aws/aws_utils.py +33 -1
  75. metaflow/plugins/aws/batch/batch.py +72 -5
  76. metaflow/plugins/aws/batch/batch_cli.py +24 -3
  77. metaflow/plugins/aws/batch/batch_decorator.py +57 -6
  78. metaflow/plugins/aws/step_functions/step_functions.py +28 -3
  79. metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
  80. metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
  81. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
  82. metaflow/plugins/cards/card_cli.py +20 -1
  83. metaflow/plugins/cards/card_creator.py +24 -1
  84. metaflow/plugins/cards/card_datastore.py +21 -49
  85. metaflow/plugins/cards/card_decorator.py +58 -6
  86. metaflow/plugins/cards/card_modules/basic.py +38 -9
  87. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  88. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  89. metaflow/plugins/cards/card_modules/components.py +592 -3
  90. metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
  91. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  92. metaflow/plugins/cards/card_modules/main.css +1 -0
  93. metaflow/plugins/cards/card_modules/main.js +56 -41
  94. metaflow/plugins/cards/card_modules/test_cards.py +22 -6
  95. metaflow/plugins/cards/component_serializer.py +1 -8
  96. metaflow/plugins/cards/metadata.py +22 -0
  97. metaflow/plugins/catch_decorator.py +9 -0
  98. metaflow/plugins/datastores/local_storage.py +12 -6
  99. metaflow/plugins/datastores/spin_storage.py +12 -0
  100. metaflow/plugins/datatools/s3/s3.py +49 -17
  101. metaflow/plugins/datatools/s3/s3op.py +113 -66
  102. metaflow/plugins/env_escape/client_modules.py +102 -72
  103. metaflow/plugins/events_decorator.py +127 -121
  104. metaflow/plugins/exit_hook/__init__.py +0 -0
  105. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  106. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  107. metaflow/plugins/kubernetes/kubernetes.py +12 -1
  108. metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
  109. metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
  110. metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
  111. metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
  112. metaflow/plugins/metadata_providers/local.py +76 -82
  113. metaflow/plugins/metadata_providers/service.py +13 -9
  114. metaflow/plugins/metadata_providers/spin.py +16 -0
  115. metaflow/plugins/package_cli.py +36 -24
  116. metaflow/plugins/parallel_decorator.py +11 -2
  117. metaflow/plugins/parsers.py +16 -0
  118. metaflow/plugins/pypi/bootstrap.py +7 -1
  119. metaflow/plugins/pypi/conda_decorator.py +41 -82
  120. metaflow/plugins/pypi/conda_environment.py +14 -6
  121. metaflow/plugins/pypi/micromamba.py +9 -1
  122. metaflow/plugins/pypi/pip.py +41 -5
  123. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  124. metaflow/plugins/pypi/utils.py +22 -0
  125. metaflow/plugins/secrets/__init__.py +3 -0
  126. metaflow/plugins/secrets/secrets_decorator.py +14 -178
  127. metaflow/plugins/secrets/secrets_func.py +49 -0
  128. metaflow/plugins/secrets/secrets_spec.py +101 -0
  129. metaflow/plugins/secrets/utils.py +74 -0
  130. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  131. metaflow/plugins/timeout_decorator.py +0 -1
  132. metaflow/plugins/uv/bootstrap.py +29 -1
  133. metaflow/plugins/uv/uv_environment.py +5 -3
  134. metaflow/pylint_wrapper.py +5 -1
  135. metaflow/runner/click_api.py +79 -26
  136. metaflow/runner/deployer.py +208 -6
  137. metaflow/runner/deployer_impl.py +32 -12
  138. metaflow/runner/metaflow_runner.py +266 -33
  139. metaflow/runner/subprocess_manager.py +21 -1
  140. metaflow/runner/utils.py +27 -16
  141. metaflow/runtime.py +660 -66
  142. metaflow/task.py +255 -26
  143. metaflow/user_configs/config_options.py +33 -21
  144. metaflow/user_configs/config_parameters.py +220 -58
  145. metaflow/user_decorators/__init__.py +0 -0
  146. metaflow/user_decorators/common.py +144 -0
  147. metaflow/user_decorators/mutable_flow.py +512 -0
  148. metaflow/user_decorators/mutable_step.py +424 -0
  149. metaflow/user_decorators/user_flow_decorator.py +264 -0
  150. metaflow/user_decorators/user_step_decorator.py +749 -0
  151. metaflow/util.py +197 -7
  152. metaflow/vendor.py +23 -7
  153. metaflow/version.py +1 -1
  154. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
  155. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
  156. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
  157. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
  158. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
  159. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  160. metaflow/_vendor/v3_5/__init__.py +0 -1
  161. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  162. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  163. metaflow/_vendor/v3_5/zipp.py +0 -329
  164. metaflow/info_file.py +0 -25
  165. metaflow/package.py +0 -203
  166. metaflow/user_configs/config_decorators.py +0 -568
  167. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
  168. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
  169. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
@@ -1,183 +1,17 @@
1
1
  import os
2
- import re
3
2
 
4
3
  from metaflow.exception import MetaflowException
5
4
  from metaflow.decorators import StepDecorator
6
5
  from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
6
+ from metaflow.plugins.secrets.secrets_spec import SecretSpec
7
+ from metaflow.plugins.secrets.utils import (
8
+ get_secrets_backend_provider,
9
+ validate_env_vars,
10
+ validate_env_vars_across_secrets,
11
+ validate_env_vars_vs_existing_env,
12
+ )
7
13
  from metaflow.unbounded_foreach import UBF_TASK
8
14
 
9
- from typing import Any, Dict, List, Union
10
-
11
- DISALLOWED_SECRETS_ENV_VAR_PREFIXES = ["METAFLOW_"]
12
-
13
-
14
- def get_default_secrets_backend_type():
15
- from metaflow.metaflow_config import DEFAULT_SECRETS_BACKEND_TYPE
16
-
17
- if DEFAULT_SECRETS_BACKEND_TYPE is None:
18
- raise MetaflowException(
19
- "No default secrets backend type configured, but needed by @secrets. "
20
- "Set METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE."
21
- )
22
- return DEFAULT_SECRETS_BACKEND_TYPE
23
-
24
-
25
- class SecretSpec:
26
- def __init__(self, secrets_backend_type, secret_id, options={}, role=None):
27
- self._secrets_backend_type = secrets_backend_type
28
- self._secret_id = secret_id
29
- self._options = options
30
- self._role = role
31
-
32
- @property
33
- def secrets_backend_type(self):
34
- return self._secrets_backend_type
35
-
36
- @property
37
- def secret_id(self):
38
- return self._secret_id
39
-
40
- @property
41
- def options(self):
42
- return self._options
43
-
44
- @property
45
- def role(self):
46
- return self._role
47
-
48
- def to_json(self):
49
- """Mainly used for testing... not the same as the input dict in secret_spec_from_dict()!"""
50
- return {
51
- "secrets_backend_type": self.secrets_backend_type,
52
- "secret_id": self.secret_id,
53
- "options": self.options,
54
- "role": self.role,
55
- }
56
-
57
- def __str__(self):
58
- return "%s (%s)" % (self._secret_id, self._secrets_backend_type)
59
-
60
- @staticmethod
61
- def secret_spec_from_str(secret_spec_str, role):
62
- # "." may be used in secret_id one day (provider specific). HOWEVER, it provides the best UX for
63
- # non-conflicting cases (i.e. for secret ids that don't contain "."). This is true for all AWS
64
- # Secrets Manager secrets.
65
- #
66
- # So we skew heavily optimize for best upfront UX for the present (1/2023).
67
- #
68
- # If/when a certain secret backend supports "." secret names, we can figure out a solution at that time.
69
- # At a minimum, dictionary style secret spec may be used with no code changes (see secret_spec_from_dict()).
70
- # Other options could be:
71
- # - accept and document that "." secret_ids don't work in Metaflow (across all possible providers)
72
- # - add a Metaflow config variable that specifies the separator (default ".")
73
- # - smarter spec parsing, that errors on secrets that look ambiguous. "aws-secrets-manager.XYZ" could mean:
74
- # + secret_id "XYZ" in aws-secrets-manager backend, OR
75
- # + secret_id "aws-secrets-manager.XYZ" default backend (if it is defined).
76
- # + in this case, user can simply set "azure-key-vault.aws-secrets-manager.XYZ" instead!
77
- parts = secret_spec_str.split(".", maxsplit=1)
78
- if len(parts) == 1:
79
- secrets_backend_type = get_default_secrets_backend_type()
80
- secret_id = parts[0]
81
- else:
82
- secrets_backend_type = parts[0]
83
- secret_id = parts[1]
84
- return SecretSpec(
85
- secrets_backend_type, secret_id=secret_id, options={}, role=role
86
- )
87
-
88
- @staticmethod
89
- def secret_spec_from_dict(secret_spec_dict, role):
90
- if "type" not in secret_spec_dict:
91
- secrets_backend_type = get_default_secrets_backend_type()
92
- else:
93
- secrets_backend_type = secret_spec_dict["type"]
94
- if not isinstance(secrets_backend_type, str):
95
- raise MetaflowException(
96
- "Bad @secrets specification - 'type' must be a string - found %s"
97
- % type(secrets_backend_type)
98
- )
99
- secret_id = secret_spec_dict.get("id")
100
- if not isinstance(secret_id, str):
101
- raise MetaflowException(
102
- "Bad @secrets specification - 'id' must be a string - found %s"
103
- % type(secret_id)
104
- )
105
- options = secret_spec_dict.get("options", {})
106
- if not isinstance(options, dict):
107
- raise MetaflowException(
108
- "Bad @secrets specification - 'option' must be a dict - found %s"
109
- % type(options)
110
- )
111
- role_for_source = secret_spec_dict.get("role", None)
112
- if role_for_source is not None:
113
- if not isinstance(role_for_source, str):
114
- raise MetaflowException(
115
- "Bad @secrets specification - 'role' must be a str - found %s"
116
- % type(role_for_source)
117
- )
118
- role = role_for_source
119
- return SecretSpec(
120
- secrets_backend_type, secret_id=secret_id, options=options, role=role
121
- )
122
-
123
-
124
- def validate_env_vars_across_secrets(all_secrets_env_vars):
125
- vars_injected_by = {}
126
- for secret_spec, env_vars in all_secrets_env_vars:
127
- for k in env_vars:
128
- if k in vars_injected_by:
129
- raise MetaflowException(
130
- "Secret '%s' will inject '%s' as env var, and it is also added by '%s'"
131
- % (secret_spec, k, vars_injected_by[k])
132
- )
133
- vars_injected_by[k] = secret_spec
134
-
135
-
136
- def validate_env_vars_vs_existing_env(all_secrets_env_vars):
137
- for secret_spec, env_vars in all_secrets_env_vars:
138
- for k in env_vars:
139
- if k in os.environ:
140
- raise MetaflowException(
141
- "Secret '%s' will inject '%s' as env var, but it already exists in env"
142
- % (secret_spec, k)
143
- )
144
-
145
-
146
- def validate_env_vars(env_vars):
147
- for k, v in env_vars.items():
148
- if not isinstance(k, str):
149
- raise MetaflowException("Found non string key %s (%s)" % (str(k), type(k)))
150
- if not isinstance(v, str):
151
- raise MetaflowException(
152
- "Found non string value %s (%s)" % (str(v), type(v))
153
- )
154
- if not re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", k):
155
- raise MetaflowException("Found invalid env var name '%s'." % k)
156
- for disallowed_prefix in DISALLOWED_SECRETS_ENV_VAR_PREFIXES:
157
- if k.startswith(disallowed_prefix):
158
- raise MetaflowException(
159
- "Found disallowed env var name '%s' (starts with '%s')."
160
- % (k, disallowed_prefix)
161
- )
162
-
163
-
164
- def get_secrets_backend_provider(secrets_backend_type):
165
- from metaflow.plugins import SECRETS_PROVIDERS
166
-
167
- try:
168
- provider_cls = [
169
- pc for pc in SECRETS_PROVIDERS if pc.TYPE == secrets_backend_type
170
- ][0]
171
- return provider_cls()
172
- except IndexError:
173
- raise MetaflowException(
174
- "Unknown secrets backend type %s (available types: %s)"
175
- % (
176
- secrets_backend_type,
177
- ", ".join(pc.TYPE for pc in SECRETS_PROVIDERS if pc.TYPE != "inline"),
178
- )
179
- )
180
-
181
15
 
182
16
  class SecretsDecorator(StepDecorator):
183
17
  """
@@ -188,13 +22,14 @@ class SecretsDecorator(StepDecorator):
188
22
  ----------
189
23
  sources : List[Union[str, Dict[str, Any]]], default: []
190
24
  List of secret specs, defining how the secrets are to be retrieved
25
+ role : str, optional, default: None
26
+ Role to use for fetching secrets
27
+ allow_override : bool, optional, default: False
28
+ Toggle whether secrets can replace existing environment variables.
191
29
  """
192
30
 
193
31
  name = "secrets"
194
- defaults = {
195
- "sources": [],
196
- "role": None,
197
- }
32
+ defaults = {"sources": [], "role": None, "allow_override": False}
198
33
 
199
34
  def task_pre_step(
200
35
  self,
@@ -276,7 +111,8 @@ class SecretsDecorator(StepDecorator):
276
111
  all_secrets_env_vars.append((secret_spec, env_vars_for_secret))
277
112
 
278
113
  validate_env_vars_across_secrets(all_secrets_env_vars)
279
- validate_env_vars_vs_existing_env(all_secrets_env_vars)
114
+ if not self.attributes["allow_override"]:
115
+ validate_env_vars_vs_existing_env(all_secrets_env_vars)
280
116
 
281
117
  # By this point
282
118
  # all_secrets_env_vars contains a list of dictionaries... env maps.
@@ -0,0 +1,49 @@
1
+ from typing import Any, Dict, Optional, Union
2
+
3
+ from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
4
+ from metaflow.exception import MetaflowException
5
+ from metaflow.plugins.secrets.secrets_spec import SecretSpec
6
+ from metaflow.plugins.secrets.utils import get_secrets_backend_provider
7
+
8
+
9
+ def get_secret(
10
+ source: Union[str, Dict[str, Any]], role: Optional[str] = None
11
+ ) -> Dict[str, str]:
12
+ """
13
+ Get secret from source
14
+
15
+ Parameters
16
+ ----------
17
+ source : Union[str, Dict[str, Any]]
18
+ Secret spec, defining how the secret is to be retrieved
19
+ role : str, optional
20
+ Role to use for fetching secrets
21
+ """
22
+ if role is None:
23
+ role = DEFAULT_SECRETS_ROLE
24
+
25
+ secret_spec = None
26
+
27
+ if isinstance(source, str):
28
+ secret_spec = SecretSpec.secret_spec_from_str(source, role=role)
29
+ elif isinstance(source, dict):
30
+ secret_spec = SecretSpec.secret_spec_from_dict(source, role=role)
31
+ else:
32
+ raise MetaflowException(
33
+ "get_secrets sources items must be either a string or a dict"
34
+ )
35
+
36
+ secrets_backend_provider = get_secrets_backend_provider(
37
+ secret_spec.secrets_backend_type
38
+ )
39
+ try:
40
+ dict_for_secret = secrets_backend_provider.get_secret_as_dict(
41
+ secret_spec.secret_id,
42
+ options=secret_spec.options,
43
+ role=secret_spec.role,
44
+ )
45
+ return dict_for_secret
46
+ except Exception as e:
47
+ raise MetaflowException(
48
+ "Failed to retrieve secret '%s': %s" % (secret_spec.secret_id, e)
49
+ )
@@ -0,0 +1,101 @@
1
+ from metaflow.exception import MetaflowException
2
+ from metaflow.plugins.secrets.utils import get_default_secrets_backend_type
3
+
4
+
5
+ class SecretSpec:
6
+ def __init__(self, secrets_backend_type, secret_id, options={}, role=None):
7
+ self._secrets_backend_type = secrets_backend_type
8
+ self._secret_id = secret_id
9
+ self._options = options
10
+ self._role = role
11
+
12
+ @property
13
+ def secrets_backend_type(self):
14
+ return self._secrets_backend_type
15
+
16
+ @property
17
+ def secret_id(self):
18
+ return self._secret_id
19
+
20
+ @property
21
+ def options(self):
22
+ return self._options
23
+
24
+ @property
25
+ def role(self):
26
+ return self._role
27
+
28
+ def to_json(self):
29
+ """Mainly used for testing... not the same as the input dict in secret_spec_from_dict()!"""
30
+ return {
31
+ "secrets_backend_type": self.secrets_backend_type,
32
+ "secret_id": self.secret_id,
33
+ "options": self.options,
34
+ "role": self.role,
35
+ }
36
+
37
+ def __str__(self):
38
+ return "%s (%s)" % (self._secret_id, self._secrets_backend_type)
39
+
40
+ @staticmethod
41
+ def secret_spec_from_str(secret_spec_str, role):
42
+ # "." may be used in secret_id one day (provider specific). HOWEVER, it provides the best UX for
43
+ # non-conflicting cases (i.e. for secret ids that don't contain "."). This is true for all AWS
44
+ # Secrets Manager secrets.
45
+ #
46
+ # So we skew heavily optimize for best upfront UX for the present (1/2023).
47
+ #
48
+ # If/when a certain secret backend supports "." secret names, we can figure out a solution at that time.
49
+ # At a minimum, dictionary style secret spec may be used with no code changes (see secret_spec_from_dict()).
50
+ # Other options could be:
51
+ # - accept and document that "." secret_ids don't work in Metaflow (across all possible providers)
52
+ # - add a Metaflow config variable that specifies the separator (default ".")
53
+ # - smarter spec parsing, that errors on secrets that look ambiguous. "aws-secrets-manager.XYZ" could mean:
54
+ # + secret_id "XYZ" in aws-secrets-manager backend, OR
55
+ # + secret_id "aws-secrets-manager.XYZ" default backend (if it is defined).
56
+ # + in this case, user can simply set "azure-key-vault.aws-secrets-manager.XYZ" instead!
57
+ parts = secret_spec_str.split(".", maxsplit=1)
58
+ if len(parts) == 1:
59
+ secrets_backend_type = get_default_secrets_backend_type()
60
+ secret_id = parts[0]
61
+ else:
62
+ secrets_backend_type = parts[0]
63
+ secret_id = parts[1]
64
+ return SecretSpec(
65
+ secrets_backend_type, secret_id=secret_id, options={}, role=role
66
+ )
67
+
68
+ @staticmethod
69
+ def secret_spec_from_dict(secret_spec_dict, role):
70
+ if "type" not in secret_spec_dict:
71
+ secrets_backend_type = get_default_secrets_backend_type()
72
+ else:
73
+ secrets_backend_type = secret_spec_dict["type"]
74
+ if not isinstance(secrets_backend_type, str):
75
+ raise MetaflowException(
76
+ "Bad @secrets specification - 'type' must be a string - found %s"
77
+ % type(secrets_backend_type)
78
+ )
79
+ secret_id = secret_spec_dict.get("id")
80
+ if not isinstance(secret_id, str):
81
+ raise MetaflowException(
82
+ "Bad @secrets specification - 'id' must be a string - found %s"
83
+ % type(secret_id)
84
+ )
85
+ options = secret_spec_dict.get("options", {})
86
+ if not isinstance(options, dict):
87
+ raise MetaflowException(
88
+ "Bad @secrets specification - 'option' must be a dict - found %s"
89
+ % type(options)
90
+ )
91
+ role_for_source = secret_spec_dict.get("role", None)
92
+ if role_for_source is not None:
93
+ if not isinstance(role_for_source, str):
94
+ raise MetaflowException(
95
+ "Bad @secrets specification - 'role' must be a str - found %s"
96
+ % type(role_for_source)
97
+ )
98
+ role = role_for_source
99
+ return SecretSpec(
100
+ secrets_backend_type, secret_id=secret_id, options=options, role=role
101
+ )
@@ -0,0 +1,74 @@
1
+ import os
2
+ import re
3
+ from metaflow.exception import MetaflowException
4
+
5
+ DISALLOWED_SECRETS_ENV_VAR_PREFIXES = ["METAFLOW_"]
6
+
7
+
8
+ def get_default_secrets_backend_type():
9
+ from metaflow.metaflow_config import DEFAULT_SECRETS_BACKEND_TYPE
10
+
11
+ if DEFAULT_SECRETS_BACKEND_TYPE is None:
12
+ raise MetaflowException(
13
+ "No default secrets backend type configured, but needed by @secrets. "
14
+ "Set METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE."
15
+ )
16
+ return DEFAULT_SECRETS_BACKEND_TYPE
17
+
18
+
19
+ def validate_env_vars_across_secrets(all_secrets_env_vars):
20
+ vars_injected_by = {}
21
+ for secret_spec, env_vars in all_secrets_env_vars:
22
+ for k in env_vars:
23
+ if k in vars_injected_by:
24
+ raise MetaflowException(
25
+ "Secret '%s' will inject '%s' as env var, and it is also added by '%s'"
26
+ % (secret_spec, k, vars_injected_by[k])
27
+ )
28
+ vars_injected_by[k] = secret_spec
29
+
30
+
31
+ def validate_env_vars_vs_existing_env(all_secrets_env_vars):
32
+ for secret_spec, env_vars in all_secrets_env_vars:
33
+ for k in env_vars:
34
+ if k in os.environ:
35
+ raise MetaflowException(
36
+ "Secret '%s' will inject '%s' as env var, but it already exists in env"
37
+ % (secret_spec, k)
38
+ )
39
+
40
+
41
+ def validate_env_vars(env_vars):
42
+ for k, v in env_vars.items():
43
+ if not isinstance(k, str):
44
+ raise MetaflowException("Found non string key %s (%s)" % (str(k), type(k)))
45
+ if not isinstance(v, str):
46
+ raise MetaflowException(
47
+ "Found non string value %s (%s)" % (str(v), type(v))
48
+ )
49
+ if not re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", k):
50
+ raise MetaflowException("Found invalid env var name '%s'." % k)
51
+ for disallowed_prefix in DISALLOWED_SECRETS_ENV_VAR_PREFIXES:
52
+ if k.startswith(disallowed_prefix):
53
+ raise MetaflowException(
54
+ "Found disallowed env var name '%s' (starts with '%s')."
55
+ % (k, disallowed_prefix)
56
+ )
57
+
58
+
59
+ def get_secrets_backend_provider(secrets_backend_type):
60
+ from metaflow.plugins import SECRETS_PROVIDERS
61
+
62
+ try:
63
+ provider_cls = [
64
+ pc for pc in SECRETS_PROVIDERS if pc.TYPE == secrets_backend_type
65
+ ][0]
66
+ return provider_cls()
67
+ except IndexError:
68
+ raise MetaflowException(
69
+ "Unknown secrets backend type %s (available types: %s)"
70
+ % (
71
+ secrets_backend_type,
72
+ ", ".join(pc.TYPE for pc in SECRETS_PROVIDERS if pc.TYPE != "inline"),
73
+ )
74
+ )
@@ -56,9 +56,9 @@ class InternalTestUnboundedForeachDecorator(StepDecorator):
56
56
  name = "unbounded_test_foreach_internal"
57
57
  results_dict = {}
58
58
 
59
- def __init__(self, attributes=None, statically_defined=False):
59
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
60
60
  super(InternalTestUnboundedForeachDecorator, self).__init__(
61
- attributes, statically_defined
61
+ attributes, statically_defined, inserted_by
62
62
  )
63
63
 
64
64
  def step_init(
@@ -38,7 +38,6 @@ class TimeoutDecorator(StepDecorator):
38
38
  defaults = {"seconds": 0, "minutes": 0, "hours": 0}
39
39
 
40
40
  def init(self):
41
- super().init()
42
41
  # Initialize secs in __init__ so other decorators could safely use this
43
42
  # value without worrying about decorator order.
44
43
  # Convert values in attributes to type:int since they can be type:str
@@ -1,10 +1,13 @@
1
1
  import os
2
+ import shutil
2
3
  import subprocess
3
4
  import sys
4
5
  import time
5
6
 
6
7
  from metaflow.util import which
8
+ from metaflow.meta_files import read_info_file
7
9
  from metaflow.metaflow_config import get_pinned_conda_libs
10
+ from metaflow.packaging_sys import MetaflowCodeContent, ContentType
8
11
  from urllib.request import Request, urlopen
9
12
  from urllib.error import URLError
10
13
 
@@ -78,11 +81,36 @@ if __name__ == "__main__":
78
81
  # return only dependency names instead of pinned versions
79
82
  return pinned.keys()
80
83
 
84
+ def skip_metaflow_dependencies():
85
+ skip_pkgs = ["metaflow", "ob-metaflow"]
86
+ info = read_info_file()
87
+ if info is not None:
88
+ try:
89
+ skip_pkgs.extend([ext_name for ext_name in info["ext_info"][0].keys()])
90
+ except Exception:
91
+ print(
92
+ "Failed to read INFO. Metaflow-related packages might get installed during runtime."
93
+ )
94
+
95
+ return skip_pkgs
96
+
81
97
  def sync_uv_project(datastore_type):
98
+ # Move the files to the current directory so uv can find them.
99
+ for filename in ["uv.lock", "pyproject.toml"]:
100
+ path_to_file = MetaflowCodeContent.get_filename(
101
+ filename, ContentType.OTHER_CONTENT
102
+ )
103
+ if path_to_file is None:
104
+ raise RuntimeError(f"Could not find {filename} in the package.")
105
+ shutil.move(path_to_file, os.path.join(os.getcwd(), filename))
106
+
82
107
  print("Syncing uv project...")
83
108
  dependencies = " ".join(get_dependencies(datastore_type))
109
+ skip_pkgs = " ".join(
110
+ [f"--no-install-package {dep}" for dep in skip_metaflow_dependencies()]
111
+ )
84
112
  cmd = f"""set -e;
85
- uv sync --frozen --no-install-package metaflow;
113
+ uv sync --frozen {skip_pkgs};
86
114
  uv pip install {dependencies} --strict
87
115
  """
88
116
  run_cmd(cmd)
@@ -2,6 +2,7 @@ import os
2
2
 
3
3
  from metaflow.exception import MetaflowException
4
4
  from metaflow.metaflow_environment import MetaflowEnvironment
5
+ from metaflow.packaging_sys import ContentType
5
6
 
6
7
 
7
8
  class UVException(MetaflowException):
@@ -12,6 +13,7 @@ class UVEnvironment(MetaflowEnvironment):
12
13
  TYPE = "uv"
13
14
 
14
15
  def __init__(self, flow):
16
+ super().__init__(flow)
15
17
  self.flow = flow
16
18
 
17
19
  def validate_environment(self, logger, datastore_type):
@@ -22,7 +24,7 @@ class UVEnvironment(MetaflowEnvironment):
22
24
  self.logger("Bootstrapping uv...")
23
25
 
24
26
  def executable(self, step_name, default=None):
25
- return "uv run python"
27
+ return "uv run --no-sync python"
26
28
 
27
29
  def add_to_package(self):
28
30
  # NOTE: We treat uv.lock and pyproject.toml as regular project assets and ship these along user code as part of the code package
@@ -43,8 +45,8 @@ class UVEnvironment(MetaflowEnvironment):
43
45
  pyproject_path = _find("pyproject.toml")
44
46
  uv_lock_path = _find("uv.lock")
45
47
  files = [
46
- (uv_lock_path, "uv.lock"),
47
- (pyproject_path, "pyproject.toml"),
48
+ (uv_lock_path, "uv.lock", ContentType.OTHER_CONTENT),
49
+ (pyproject_path, "pyproject.toml", ContentType.OTHER_CONTENT),
48
50
  ]
49
51
  return files
50
52
 
@@ -28,7 +28,11 @@ class PyLint(object):
28
28
  return self._run is not None
29
29
 
30
30
  def run(self, logger=None, warnings=False, pylint_config=[]):
31
- args = [self._fname]
31
+ args = [
32
+ self._fname,
33
+ "--signature-mutators",
34
+ "metaflow.user_decorators.user_step_decorator.user_step_decorator",
35
+ ]
32
36
  if not warnings:
33
37
  args.append("--errors-only")
34
38
  if pylint_config: