ob-metaflow 2.16.8.2rc2__py2.py3-none-any.whl → 2.17.1.0__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.

Potentially problematic release.


This version of ob-metaflow might be problematic. Click here for more details.

Files changed (65) hide show
  1. metaflow/_vendor/click/core.py +3 -4
  2. metaflow/_vendor/imghdr/__init__.py +7 -1
  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/cli.py +11 -2
  21. metaflow/cli_components/run_cmds.py +0 -15
  22. metaflow/client/core.py +6 -1
  23. metaflow/cmd/make_wrapper.py +30 -0
  24. metaflow/extension_support/__init__.py +4 -3
  25. metaflow/flowspec.py +1 -113
  26. metaflow/graph.py +10 -134
  27. metaflow/lint.py +3 -70
  28. metaflow/metaflow_environment.py +14 -6
  29. metaflow/metaflow_version.py +15 -0
  30. metaflow/package/__init__.py +18 -9
  31. metaflow/packaging_sys/__init__.py +53 -43
  32. metaflow/packaging_sys/backend.py +21 -6
  33. metaflow/packaging_sys/tar_backend.py +16 -3
  34. metaflow/packaging_sys/v1.py +21 -21
  35. metaflow/plugins/argo/argo_client.py +31 -14
  36. metaflow/plugins/argo/argo_workflows.py +67 -22
  37. metaflow/plugins/argo/argo_workflows_cli.py +348 -85
  38. metaflow/plugins/argo/argo_workflows_deployer_objects.py +69 -0
  39. metaflow/plugins/aws/step_functions/step_functions.py +0 -6
  40. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
  41. metaflow/plugins/cards/card_modules/basic.py +3 -14
  42. metaflow/plugins/cards/card_modules/convert_to_native_type.py +7 -1
  43. metaflow/plugins/kubernetes/kubernetes_decorator.py +1 -1
  44. metaflow/plugins/kubernetes/kubernetes_job.py +8 -2
  45. metaflow/plugins/kubernetes/kubernetes_jobsets.py +26 -28
  46. metaflow/plugins/pypi/conda_decorator.py +4 -2
  47. metaflow/runner/click_api.py +14 -7
  48. metaflow/runner/deployer.py +160 -7
  49. metaflow/runner/deployer_impl.py +15 -7
  50. metaflow/runner/subprocess_manager.py +20 -12
  51. metaflow/runtime.py +27 -102
  52. metaflow/task.py +25 -46
  53. metaflow/user_decorators/mutable_flow.py +8 -6
  54. metaflow/util.py +0 -29
  55. metaflow/vendor.py +23 -6
  56. metaflow/version.py +1 -1
  57. {ob_metaflow-2.16.8.2rc2.data → ob_metaflow-2.17.1.0.data}/data/share/metaflow/devtools/Makefile +3 -2
  58. {ob_metaflow-2.16.8.2rc2.data → ob_metaflow-2.17.1.0.data}/data/share/metaflow/devtools/Tiltfile +2 -2
  59. {ob_metaflow-2.16.8.2rc2.dist-info → ob_metaflow-2.17.1.0.dist-info}/METADATA +2 -2
  60. {ob_metaflow-2.16.8.2rc2.dist-info → ob_metaflow-2.17.1.0.dist-info}/RECORD +65 -48
  61. {ob_metaflow-2.16.8.2rc2.data → ob_metaflow-2.17.1.0.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  62. {ob_metaflow-2.16.8.2rc2.dist-info → ob_metaflow-2.17.1.0.dist-info}/WHEEL +0 -0
  63. {ob_metaflow-2.16.8.2rc2.dist-info → ob_metaflow-2.17.1.0.dist-info}/entry_points.txt +0 -0
  64. {ob_metaflow-2.16.8.2rc2.dist-info → ob_metaflow-2.17.1.0.dist-info}/licenses/LICENSE +0 -0
  65. {ob_metaflow-2.16.8.2rc2.dist-info → ob_metaflow-2.17.1.0.dist-info}/top_level.txt +0 -0
@@ -57,6 +57,15 @@ class PackagingBackend(ABC):
57
57
  """Open the archive from the given content."""
58
58
  pass
59
59
 
60
+ @classmethod
61
+ @abstractmethod
62
+ def cls_member_name(cls, member: Union[Any, str]) -> str:
63
+ """
64
+ Returns the name of the member as a string.
65
+ This is used to ensure consistent naming across different archive formats.
66
+ """
67
+ pass
68
+
60
69
  @classmethod
61
70
  @abstractmethod
62
71
  def cls_has_member(cls, archive: Any, name: str) -> bool:
@@ -72,14 +81,20 @@ class PackagingBackend(ABC):
72
81
  def cls_extract_members(
73
82
  cls,
74
83
  archive: Any,
75
- members: Optional[List[str]] = None,
84
+ members: Optional[List[Any]] = None,
76
85
  dest_dir: str = ".",
77
86
  ) -> None:
78
87
  pass
79
88
 
80
89
  @classmethod
81
90
  @abstractmethod
82
- def cls_list_members(cls, archive: Any) -> Optional[List[str]]:
91
+ def cls_list_names(cls, archive: Any) -> Optional[List[str]]:
92
+ pass
93
+
94
+ @classmethod
95
+ @abstractmethod
96
+ def cls_list_members(cls, archive: Any) -> Optional[List[Any]]:
97
+ """List all members in the archive."""
83
98
  pass
84
99
 
85
100
  def has_member(self, name: str) -> bool:
@@ -93,17 +108,17 @@ class PackagingBackend(ABC):
93
108
  raise ValueError("Cannot get member from an uncreated archive")
94
109
 
95
110
  def extract_members(
96
- self, members: Optional[List[str]] = None, dest_dir: str = "."
111
+ self, members: Optional[List[Any]] = None, dest_dir: str = "."
97
112
  ) -> None:
98
113
  if self._archive:
99
114
  self.cls_extract_members(self._archive, members, dest_dir)
100
115
  else:
101
116
  raise ValueError("Cannot extract from an uncreated archive")
102
117
 
103
- def list_members(self) -> Optional[List[str]]:
118
+ def list_names(self) -> Optional[List[str]]:
104
119
  if self._archive:
105
- return self.cls_list_members(self._archive)
106
- raise ValueError("Cannot list members from an uncreated archive")
120
+ return self.cls_list_names(self._archive)
121
+ raise ValueError("Cannot list names from an uncreated archive")
107
122
 
108
123
  def __enter__(self):
109
124
  self.create()
@@ -1,7 +1,7 @@
1
1
  import tarfile
2
2
 
3
3
  from io import BytesIO
4
- from typing import IO, List, Optional, Union
4
+ from typing import Any, IO, List, Optional, Union
5
5
 
6
6
  from .backend import PackagingBackend
7
7
 
@@ -56,6 +56,13 @@ class TarPackagingBackend(PackagingBackend):
56
56
  def cls_open(cls, content: IO[bytes]) -> tarfile.TarFile:
57
57
  return tarfile.open(fileobj=content, mode="r:gz")
58
58
 
59
+ @classmethod
60
+ def cls_member_name(cls, member: Union[tarfile.TarInfo, str]) -> str:
61
+ """
62
+ Returns the name of the member as a string.
63
+ """
64
+ return member.name if isinstance(member, tarfile.TarInfo) else member
65
+
59
66
  @classmethod
60
67
  def cls_has_member(cls, archive: tarfile.TarFile, name: str) -> bool:
61
68
  try:
@@ -76,11 +83,17 @@ class TarPackagingBackend(PackagingBackend):
76
83
  def cls_extract_members(
77
84
  cls,
78
85
  archive: tarfile.TarFile,
79
- members: Optional[List[str]] = None,
86
+ members: Optional[List[Any]] = None,
80
87
  dest_dir: str = ".",
81
88
  ) -> None:
82
89
  archive.extractall(path=dest_dir, members=members)
83
90
 
84
91
  @classmethod
85
- def cls_list_members(cls, archive: tarfile.TarFile) -> Optional[List[str]]:
92
+ def cls_list_members(
93
+ cls, archive: tarfile.TarFile
94
+ ) -> Optional[List[tarfile.TarInfo]]:
95
+ return archive.getmembers() or None
96
+
97
+ @classmethod
98
+ def cls_list_names(cls, archive: tarfile.TarFile) -> Optional[List[str]]:
86
99
  return archive.getnames() or None
@@ -61,23 +61,25 @@ class MetaflowCodeContentV1(MetaflowCodeContentV1Base):
61
61
  else:
62
62
  new_modules = []
63
63
 
64
- self._modules = {
65
- name: _ModuleInfo(
66
- name,
67
- set(
68
- Path(p).resolve().as_posix()
69
- for p in getattr(mod, "__path__", [mod.__file__])
70
- ),
71
- mod,
72
- True, # This is a Metaflow module (see filter below)
73
- )
74
- for (name, mod) in new_modules
75
- }
76
-
77
- # Filter the modules
78
- self._modules = {
79
- name: info for name, info in self._modules.items() if criteria(info.module)
80
- }
64
+ self._modules = {} # type: Dict[str, _ModuleInfo]
65
+ # We do this explicitly module by module to harden it against misbehaving
66
+ # modules like the one in:
67
+ # https://github.com/Netflix/metaflow/issues/2512
68
+ # We will silently ignore modules that are not well built.
69
+ for name, mod in new_modules:
70
+ try:
71
+ minfo = _ModuleInfo(
72
+ name,
73
+ set(
74
+ Path(p).resolve().as_posix()
75
+ for p in getattr(mod, "__path__", [mod.__file__])
76
+ ),
77
+ mod,
78
+ True, # This is a Metaflow module (see filter below)
79
+ )
80
+ except:
81
+ continue
82
+ self._modules[name] = minfo
81
83
 
82
84
  # Contain metadata information regarding the distributions packaged.
83
85
  # This allows Metaflow to "fake" distribution information when packaged
@@ -355,16 +357,14 @@ class MetaflowCodeContentV1(MetaflowCodeContentV1Base):
355
357
  )
356
358
  yield json.dumps(self.create_mfcontent_info()).encode(
357
359
  "utf-8"
358
- ), os.path.join(self._code_dir, MFCONTENT_MARKER)
360
+ ), MFCONTENT_MARKER
359
361
  else:
360
362
  for k in self._other_content.keys():
361
363
  yield "<generated %s content>" % (os.path.basename(k)), k
362
364
  yield "<generated %s content>" % (
363
365
  os.path.basename(self._dist_info_file)
364
366
  ), os.path.join(self._other_dir, self._dist_info_file)
365
- yield "<generated %s content>" % MFCONTENT_MARKER, os.path.join(
366
- self._code_dir, MFCONTENT_MARKER
367
- )
367
+ yield "<generated %s content>" % MFCONTENT_MARKER, MFCONTENT_MARKER
368
368
 
369
369
  def _metaflow_distribution_files(self) -> Generator[Tuple[str, str], None, None]:
370
370
  debug.package_exec("Including Metaflow from '%s'" % self._metaflow_root)
@@ -58,21 +58,38 @@ class ArgoClient(object):
58
58
  json.loads(e.body)["message"] if e.body is not None else e.reason
59
59
  )
60
60
 
61
- def get_workflow_templates(self):
61
+ def get_workflow_templates(self, page_size=100):
62
62
  client = self._client.get()
63
- try:
64
- return client.CustomObjectsApi().list_namespaced_custom_object(
65
- group=self._group,
66
- version=self._version,
67
- namespace=self._namespace,
68
- plural="workflowtemplates",
69
- )["items"]
70
- except client.rest.ApiException as e:
71
- if e.status == 404:
72
- return None
73
- raise ArgoClientException(
74
- json.loads(e.body)["message"] if e.body is not None else e.reason
75
- )
63
+ continue_token = None
64
+
65
+ while True:
66
+ try:
67
+ params = {"limit": page_size}
68
+ if continue_token:
69
+ params["_continue"] = continue_token
70
+
71
+ response = client.CustomObjectsApi().list_namespaced_custom_object(
72
+ group=self._group,
73
+ version=self._version,
74
+ namespace=self._namespace,
75
+ plural="workflowtemplates",
76
+ **params,
77
+ )
78
+
79
+ for item in response.get("items", []):
80
+ yield item
81
+
82
+ metadata = response.get("metadata", {})
83
+ continue_token = metadata.get("continue")
84
+
85
+ if not continue_token:
86
+ break
87
+ except client.rest.ApiException as e:
88
+ if e.status == 404:
89
+ return None
90
+ raise ArgoClientException(
91
+ json.loads(e.body)["message"] if e.body is not None else e.reason
92
+ )
76
93
 
77
94
  def register_workflow_template(self, name, workflow_template):
78
95
  # Unfortunately, Kubernetes client does not handle optimistic
@@ -216,23 +216,14 @@ class ArgoWorkflows(object):
216
216
  return name.replace(".", "-")
217
217
 
218
218
  @staticmethod
219
- def list_templates(flow_name, all=False):
219
+ def list_templates(flow_name, all=False, page_size=100):
220
220
  client = ArgoClient(namespace=KUBERNETES_NAMESPACE)
221
221
 
222
- templates = client.get_workflow_templates()
223
- if templates is None:
224
- return []
225
-
226
- template_names = [
227
- template["metadata"]["name"]
228
- for template in templates
229
- if all
230
- or flow_name
231
- == template["metadata"]
232
- .get("annotations", {})
233
- .get("metaflow/flow_name", None)
234
- ]
235
- return template_names
222
+ for template in client.get_workflow_templates(page_size=page_size):
223
+ if all or flow_name == template["metadata"].get("annotations", {}).get(
224
+ "metaflow/flow_name", None
225
+ ):
226
+ yield template["metadata"]["name"]
236
227
 
237
228
  @staticmethod
238
229
  def delete(name):
@@ -271,6 +262,7 @@ class ArgoWorkflows(object):
271
262
  flow_name=flow_name, run_id=name
272
263
  )
273
264
  )
265
+ return True
274
266
 
275
267
  @staticmethod
276
268
  def get_workflow_status(flow_name, name):
@@ -965,11 +957,6 @@ class ArgoWorkflows(object):
965
957
  dag_task = DAGTask(self._sanitize(node.name)).template(
966
958
  self._sanitize(node.name)
967
959
  )
968
- if node.type == "split-switch":
969
- raise ArgoWorkflowsException(
970
- "Deploying flows with switch statement "
971
- "to Argo Workflows is not supported currently."
972
- )
973
960
  elif (
974
961
  node.is_inside_foreach
975
962
  and self.graph[node.in_funcs[0]].type == "foreach"
@@ -3291,8 +3278,8 @@ class ArgoWorkflows(object):
3291
3278
  Trigger().template(
3292
3279
  TriggerTemplate(self.name)
3293
3280
  # Trigger a deployed workflow template
3294
- .argo_workflow_trigger(
3295
- ArgoWorkflowTrigger()
3281
+ .k8s_trigger(
3282
+ StandardK8STrigger()
3296
3283
  .source(
3297
3284
  {
3298
3285
  "resource": {
@@ -4270,6 +4257,10 @@ class TriggerTemplate(object):
4270
4257
  self.payload = tree()
4271
4258
  self.payload["name"] = name
4272
4259
 
4260
+ def k8s_trigger(self, k8s_trigger):
4261
+ self.payload["k8s"] = k8s_trigger.to_json()
4262
+ return self
4263
+
4273
4264
  def argo_workflow_trigger(self, argo_workflow_trigger):
4274
4265
  self.payload["argoWorkflow"] = argo_workflow_trigger.to_json()
4275
4266
  return self
@@ -4344,3 +4335,57 @@ class TriggerParameter(object):
4344
4335
 
4345
4336
  def __str__(self):
4346
4337
  return json.dumps(self.payload, indent=4)
4338
+
4339
+
4340
+ class StandardK8STrigger(object):
4341
+ # https://pkg.go.dev/github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1#StandardK8STrigger
4342
+
4343
+ def __init__(self):
4344
+ tree = lambda: defaultdict(tree)
4345
+ self.payload = tree()
4346
+ self.payload["operation"] = "create"
4347
+
4348
+ def operation(self, operation):
4349
+ self.payload["operation"] = operation
4350
+ return self
4351
+
4352
+ def group(self, group):
4353
+ self.payload["group"] = group
4354
+ return self
4355
+
4356
+ def version(self, version):
4357
+ self.payload["version"] = version
4358
+ return self
4359
+
4360
+ def resource(self, resource):
4361
+ self.payload["resource"] = resource
4362
+ return self
4363
+
4364
+ def namespace(self, namespace):
4365
+ self.payload["namespace"] = namespace
4366
+ return self
4367
+
4368
+ def source(self, source):
4369
+ self.payload["source"] = source
4370
+ return self
4371
+
4372
+ def parameters(self, trigger_parameters):
4373
+ if "parameters" not in self.payload:
4374
+ self.payload["parameters"] = []
4375
+ for trigger_parameter in trigger_parameters:
4376
+ self.payload["parameters"].append(trigger_parameter.to_json())
4377
+ return self
4378
+
4379
+ def live_object(self, live_object=True):
4380
+ self.payload["liveObject"] = live_object
4381
+ return self
4382
+
4383
+ def patch_strategy(self, patch_strategy):
4384
+ self.payload["patchStrategy"] = patch_strategy
4385
+ return self
4386
+
4387
+ def to_json(self):
4388
+ return self.payload
4389
+
4390
+ def __str__(self):
4391
+ return json.dumps(self.payload, indent=4)