metaflow 2.18.11__py2.py3-none-any.whl → 2.18.12__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.
@@ -121,6 +121,8 @@ class ArgoWorkflows(object):
121
121
  incident_io_metadata: List[str] = None,
122
122
  enable_heartbeat_daemon=True,
123
123
  enable_error_msg_capture=False,
124
+ workflow_title=None,
125
+ workflow_description=None,
124
126
  ):
125
127
  # Some high-level notes -
126
128
  #
@@ -177,6 +179,8 @@ class ArgoWorkflows(object):
177
179
  )
178
180
  self.enable_heartbeat_daemon = enable_heartbeat_daemon
179
181
  self.enable_error_msg_capture = enable_error_msg_capture
182
+ self.workflow_title = workflow_title
183
+ self.workflow_description = workflow_description
180
184
  self.parameters = self._process_parameters()
181
185
  self.config_parameters = self._process_config_parameters()
182
186
  self.triggers, self.trigger_options = self._process_triggers()
@@ -430,6 +434,25 @@ class ArgoWorkflows(object):
430
434
  "metaflow/project_flow_name": current.project_flow_name,
431
435
  }
432
436
  )
437
+
438
+ # Add Argo Workflows title and description annotations
439
+ # https://argo-workflows.readthedocs.io/en/latest/title-and-description/
440
+ # Use CLI-provided values or auto-populate from metadata
441
+ title = (
442
+ (self.workflow_title.strip() if self.workflow_title else None)
443
+ or current.get("project_flow_name")
444
+ or self.flow.name
445
+ )
446
+
447
+ description = (
448
+ self.workflow_description.strip() if self.workflow_description else None
449
+ ) or (self.flow.__doc__.strip() if self.flow.__doc__ else None)
450
+
451
+ if title:
452
+ annotations["workflows.argoproj.io/title"] = title
453
+ if description:
454
+ annotations["workflows.argoproj.io/description"] = description
455
+
433
456
  return annotations
434
457
 
435
458
  def _get_schedule(self):
@@ -894,7 +917,16 @@ class ArgoWorkflows(object):
894
917
  .annotations(
895
918
  {
896
919
  **annotations,
897
- **self._base_annotations,
920
+ **{
921
+ k: v
922
+ for k, v in self._base_annotations.items()
923
+ if k
924
+ # Skip custom title/description for workflows as this makes it harder to find specific runs.
925
+ not in [
926
+ "workflows.argoproj.io/title",
927
+ "workflows.argoproj.io/description",
928
+ ]
929
+ },
898
930
  **{"metaflow/run_id": "argo-{{workflow.name}}"},
899
931
  }
900
932
  )
@@ -227,6 +227,18 @@ def argo_workflows(obj, name=None):
227
227
  show_default=True,
228
228
  help="Capture stack trace of first failed task in exit hook.",
229
229
  )
230
+ @click.option(
231
+ "--workflow-title",
232
+ default=None,
233
+ type=str,
234
+ help="Custom title for the workflow displayed in Argo Workflows UI. Defaults to `project_flow_name`. Supports markdown formatting.",
235
+ )
236
+ @click.option(
237
+ "--workflow-description",
238
+ default=None,
239
+ type=str,
240
+ help="Custom description for the workflow displayed in Argo Workflows UI. Defaults to the flow's docstring if available. Supports markdown formatting and multi-line text.",
241
+ )
230
242
  @click.pass_obj
231
243
  def create(
232
244
  obj,
@@ -248,6 +260,8 @@ def create(
248
260
  incident_io_alert_source_config_id=None,
249
261
  incident_io_metadata=None,
250
262
  enable_heartbeat_daemon=True,
263
+ workflow_title=None,
264
+ workflow_description=None,
251
265
  deployer_attribute_file=None,
252
266
  enable_error_msg_capture=False,
253
267
  ):
@@ -312,6 +326,8 @@ def create(
312
326
  incident_io_metadata,
313
327
  enable_heartbeat_daemon,
314
328
  enable_error_msg_capture,
329
+ workflow_title,
330
+ workflow_description,
315
331
  )
316
332
 
317
333
  if only_json:
@@ -658,6 +674,8 @@ def make_flow(
658
674
  incident_io_metadata,
659
675
  enable_heartbeat_daemon,
660
676
  enable_error_msg_capture,
677
+ workflow_title,
678
+ workflow_description,
661
679
  ):
662
680
  # TODO: Make this check less specific to Amazon S3 as we introduce
663
681
  # support for more cloud object stores.
@@ -750,6 +768,8 @@ def make_flow(
750
768
  incident_io_metadata=incident_io_metadata,
751
769
  enable_heartbeat_daemon=enable_heartbeat_daemon,
752
770
  enable_error_msg_capture=enable_error_msg_capture,
771
+ workflow_title=workflow_title,
772
+ workflow_description=workflow_description,
753
773
  )
754
774
 
755
775
 
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import os
3
- from datetime import datetime
3
+ from datetime import datetime, timezone
4
4
 
5
5
  ###
6
6
  # Algorithm to determine 1st error:
@@ -26,6 +26,9 @@ def parse_workflow_failures():
26
26
  def group_failures_by_template(failures):
27
27
  groups = {}
28
28
  for failure in failures:
29
+ if failure.get("finishedAt", None) is None:
30
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
31
+ failure["finishedAt"] = timestamp
29
32
  groups.setdefault(failure["templateName"], []).append(failure)
30
33
  return groups
31
34
 
@@ -53,7 +56,7 @@ def determine_first_error():
53
56
  grouped_failures = group_failures_by_template(failures)
54
57
  for group in grouped_failures.values():
55
58
  group.sort(
56
- key=lambda x: datetime.strptime(x["finishedAt"], "%Y-%m-%dT%H:%M:%SZ")
59
+ key=lambda g: datetime.strptime(g["finishedAt"], "%Y-%m-%dT%H:%M:%SZ")
57
60
  )
58
61
 
59
62
  earliest_group = grouped_failures[
@@ -15,6 +15,7 @@ from .renderer_tools import render_safely
15
15
  from .json_viewer import JSONViewer as _JSONViewer, YAMLViewer as _YAMLViewer
16
16
  import uuid
17
17
  import inspect
18
+ import textwrap
18
19
 
19
20
 
20
21
  def _warning_with_component(component, msg):
@@ -656,19 +657,38 @@ class Markdown(UserComponent):
656
657
  )
657
658
  ```
658
659
 
660
+ Multi-line strings with indentation are automatically dedented:
661
+ ```
662
+ current.card.append(
663
+ Markdown(f'''
664
+ # Header
665
+ - Item 1
666
+ - Item 2
667
+ ''')
668
+ )
669
+ ```
670
+
659
671
  Parameters
660
672
  ----------
661
673
  text : str
662
- Text formatted in Markdown.
674
+ Text formatted in Markdown. Leading whitespace common to all lines
675
+ is automatically removed to support indented multi-line strings.
663
676
  """
664
677
 
665
678
  REALTIME_UPDATABLE = True
666
679
 
680
+ @staticmethod
681
+ def _dedent_text(text):
682
+ """Remove common leading whitespace from all lines."""
683
+ if text is None:
684
+ return None
685
+ return textwrap.dedent(text)
686
+
667
687
  def update(self, text=None):
668
- self._text = text
688
+ self._text = self._dedent_text(text)
669
689
 
670
690
  def __init__(self, text=None):
671
- self._text = text
691
+ self._text = self._dedent_text(text)
672
692
 
673
693
  @with_default_component_id
674
694
  @render_safely
metaflow/runner/utils.py CHANGED
@@ -109,7 +109,6 @@ def read_from_fifo_when_ready(
109
109
  content = bytearray()
110
110
  poll = select.poll()
111
111
  poll.register(fifo_fd, select.POLLIN)
112
- max_timeout = 3 # Wait for 10 * 3 = 30 ms after last write
113
112
  while True:
114
113
  if check_process_exited(command_obj) and command_obj.process.returncode != 0:
115
114
  raise CalledProcessError(
@@ -137,15 +136,16 @@ def read_from_fifo_when_ready(
137
136
  else:
138
137
  # We had no events (just a timeout) and the read didn't return
139
138
  # an exception so the file is still open; we continue waiting for data
140
- # Unfortunately, on MacOS, it seems that even *after* the file is
141
- # closed on the other end, we still don't get a BlockingIOError so
142
- # we hack our way and timeout if there is no write in 30ms which is
143
- # a relative eternity for file writes.
144
- if content:
145
- if max_timeout <= 0:
146
- break
147
- max_timeout -= 1
148
- continue
139
+ # On some systems (notably MacOS), even after the file is closed on the
140
+ # other end, we may not get a BlockingIOError or proper EOF signal.
141
+ # Instead of using an arbitrary timeout, check if the writer process
142
+ # has actually exited. If it has and we have content, we can safely
143
+ # assume EOF. If the process is still running, continue waiting.
144
+ if content and check_process_exited(command_obj):
145
+ # Process has exited and we got an empty read with no poll events.
146
+ # This is EOF - break out to return the content we've collected.
147
+ break
148
+ # else: process is still running, continue waiting for more data
149
149
  except BlockingIOError:
150
150
  has_blocking_error = True
151
151
  if content:
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.18.11"
1
+ metaflow_version = "2.18.12"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaflow
3
- Version: 2.18.11
3
+ Version: 2.18.12
4
4
  Summary: Metaflow: More AI and ML, Less Engineering
5
5
  Author: Metaflow Developers
6
6
  Author-email: help@metaflow.org
@@ -26,7 +26,7 @@ License-File: LICENSE
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: boto3
28
28
  Provides-Extra: stubs
29
- Requires-Dist: metaflow-stubs==2.18.11; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.18.12; extra == "stubs"
30
30
  Dynamic: author
31
31
  Dynamic: author-email
32
32
  Dynamic: classifier
@@ -36,7 +36,7 @@ metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
36
36
  metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
37
37
  metaflow/util.py,sha256=g2SOU_CRzJLgDM_UGF9QDMANMAIHAsDRXE6S76_YzsY,14594
38
38
  metaflow/vendor.py,sha256=A82CGHfStZGDP5pQ5XzRjFkbN1ZC-vFmghXIrzMDDNg,5868
39
- metaflow/version.py,sha256=q3OkFrD1pVtgUDvcF5KfpBd3SSEX2AEsbdqVS9ReFy4,29
39
+ metaflow/version.py,sha256=LEAeq12k2u6Mfx_BKRFo6XFbIXmlvtKvNKon7OjzR2k,29
40
40
  metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
41
41
  metaflow/_vendor/typing_extensions.py,sha256=q9zxWa6p6CzF1zZvSkygSlklduHf_b3K7MCxGz7MJRc,134519
42
42
  metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
@@ -231,12 +231,12 @@ metaflow/plugins/airflow/sensors/s3_sensor.py,sha256=iDReG-7FKnumrtQg-HY6cCUAAqN
231
231
  metaflow/plugins/argo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
232
  metaflow/plugins/argo/argo_client.py,sha256=oT4ZrCyE7CYEbqNN0SfoZfSHd5fYW9XtuOrQEiUd1co,17230
233
233
  metaflow/plugins/argo/argo_events.py,sha256=_C1KWztVqgi3zuH57pInaE9OzABc2NnncC-zdwOMZ-w,5909
234
- metaflow/plugins/argo/argo_workflows.py,sha256=r__mx04QOPbpBEe2c7F23NKn93-KPWhmU7LCh0Gx2cw,216218
235
- metaflow/plugins/argo/argo_workflows_cli.py,sha256=-blfZp-kAS8oWFTarfou9gRyE4QCnnJwa-0g8QuE0zk,52280
234
+ metaflow/plugins/argo/argo_workflows.py,sha256=89C72toG6tzsix8oZ3wr5DBEn_88GjydEWzvcL3L1S0,217637
235
+ metaflow/plugins/argo/argo_workflows_cli.py,sha256=GRDsiE8QT9HEdeAUODtyyGiqePhShSkwJJ8tiPQwwiI,52992
236
236
  metaflow/plugins/argo/argo_workflows_decorator.py,sha256=CLSjPqFTGucZ2_dSQGAYkoWWUZBQ9TCBXul4rxhDj3w,8282
237
237
  metaflow/plugins/argo/argo_workflows_deployer.py,sha256=6kHxEnYXJwzNCM9swI8-0AckxtPWqwhZLerYkX8fxUM,4444
238
238
  metaflow/plugins/argo/argo_workflows_deployer_objects.py,sha256=ydBE-lP42eNKvep36nQdUBPS3rQQErvoA7rCgyp5M6I,14949
239
- metaflow/plugins/argo/capture_error.py,sha256=Ys9dscGrTpW-ZCirLBU0gD9qBM0BjxyxGlUMKcwewQc,1852
239
+ metaflow/plugins/argo/capture_error.py,sha256=9ggbGHyR9UBPswOqAHCbpE9kdXuV3uBYslKiybFxqhs,2042
240
240
  metaflow/plugins/argo/conditional_input_paths.py,sha256=Vtca74XbhnqAXgJJXKasLEa28jZbKBZPC5w4NAIOURc,1251
241
241
  metaflow/plugins/argo/exit_hooks.py,sha256=nh8IEkzAtQnbKVnh3N9CVnVKZB39Bjm3e0LFrACsLz8,6109
242
242
  metaflow/plugins/argo/generate_input_paths.py,sha256=loYsI6RFX9LlFsHb7Fe-mzlTTtRdySoOu7sYDy-uXK0,881
@@ -287,7 +287,7 @@ metaflow/plugins/cards/card_modules/base.html,sha256=Y208ZKIZqEWWUcoBFTLTdWKAG0C
287
287
  metaflow/plugins/cards/card_modules/basic.py,sha256=bKU_b_Wfs2OC6me6kPp1Jm_SLvaJ4dkUCMAt2VLampk,26430
288
288
  metaflow/plugins/cards/card_modules/bundle.css,sha256=zlYjv5rt7lMqiQzd_OAe4QdQeM3J3YbwljnEghlbTaU,28052
289
289
  metaflow/plugins/cards/card_modules/card.py,sha256=6sbqP5mwf7QWvQvX2N_bC78H9ixuI5sQ8612Q5islys,4627
290
- metaflow/plugins/cards/card_modules/components.py,sha256=hF204MUyJ2DZXSPhEt7d23isjdEf4TGK8pzmVkmXyQ0,45574
290
+ metaflow/plugins/cards/card_modules/components.py,sha256=z9IT9gVpI22hUhGtJbgcy5oMpGzPeTof_lEkOOfMa9Q,46150
291
291
  metaflow/plugins/cards/card_modules/convert_to_native_type.py,sha256=opjGOvWg7trOqzhTRZ7h1cFlYrNj68Wn3_EyDTIKEvw,17320
292
292
  metaflow/plugins/cards/card_modules/json_viewer.py,sha256=DWOcQPk6DXbeGuYEqv1gGrQW1eMVYlCXv3kMsVLnhNc,7113
293
293
  metaflow/plugins/cards/card_modules/main.css,sha256=avu7BTB9qj0M8LvqNLUhikUFQhmAJhQQ7REcUgh9zMw,11725
@@ -384,7 +384,7 @@ metaflow/runner/metaflow_runner.py,sha256=uo3BzcAfZ67VT_f-TPe5ZHiWHn6uuojWusOMGk
384
384
  metaflow/runner/nbdeploy.py,sha256=Sp5w-6nCZwjHaRBHWxi8udya-RYnJOB76KNLjB4L7Gs,4166
385
385
  metaflow/runner/nbrun.py,sha256=LhJu-Teoi7wTkNxg0kpNPVXFxH_9P4lvtp0ysMEIFJ8,7299
386
386
  metaflow/runner/subprocess_manager.py,sha256=x-MtPpGGMQUkIbQ_oNOuR-45b91DFvsCJ0SPoFc0dF4,23028
387
- metaflow/runner/utils.py,sha256=fU4vPazBdi6ATAUW_DaBAQeVslRwrLT8Pn9s5wav3gg,10350
387
+ metaflow/runner/utils.py,sha256=x9EED11RZGf7J4OuK-cVuj8gxGlSjRQFSqqTehvBSjw,10588
388
388
  metaflow/sidecar/__init__.py,sha256=1mmNpmQ5puZCpRmmYlCOeieZ4108Su9XQ4_EqF1FGOU,131
389
389
  metaflow/sidecar/sidecar.py,sha256=EspKXvPPNiyRToaUZ51PS5TT_PzrBNAurn_wbFnmGr0,1334
390
390
  metaflow/sidecar/sidecar_messages.py,sha256=zPsCoYgDIcDkkvdC9MEpJTJ3y6TSGm2JWkRc4vxjbFA,1071
@@ -430,12 +430,12 @@ metaflow/user_decorators/mutable_flow.py,sha256=EywKTN3cnXPQF_s62wQaC4a4aH14j8oe
430
430
  metaflow/user_decorators/mutable_step.py,sha256=-BY0UDXf_RCAEnC5JlLzEXGdiw1KD9oSrSxS_SWaB9Y,16791
431
431
  metaflow/user_decorators/user_flow_decorator.py,sha256=2yDwZq9QGv9W-7kEuKwa8o4ZkTvuHJ5ESz7VVrGViAI,9890
432
432
  metaflow/user_decorators/user_step_decorator.py,sha256=4558NR8RJtN22OyTwCXO80bAMhMTaRGMoX12b1GMcPc,27232
433
- metaflow-2.18.11.data/data/share/metaflow/devtools/Makefile,sha256=TT4TCq8ALSfqYyGqDPocN5oPcZe2FqoCZxmGO1LmyCc,13760
434
- metaflow-2.18.11.data/data/share/metaflow/devtools/Tiltfile,sha256=b6l_fjDO0wtxOkO85lFvuf3HDa6wnzHhhBG8yv1kQqk,23949
435
- metaflow-2.18.11.data/data/share/metaflow/devtools/pick_services.sh,sha256=PGjQeDIigFHeoQ0asmYNdYDPIOdeYy1UYvkw2wdN4zg,2209
436
- metaflow-2.18.11.dist-info/licenses/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
437
- metaflow-2.18.11.dist-info/METADATA,sha256=7lnNSVzLGJoGR8_RscIBX2R55lbQPIU1QqUOlUuWiUo,6743
438
- metaflow-2.18.11.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
439
- metaflow-2.18.11.dist-info/entry_points.txt,sha256=RvEq8VFlgGe_FfqGOZi0D7ze1hLD0pAtXeNyGfzc_Yc,103
440
- metaflow-2.18.11.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
441
- metaflow-2.18.11.dist-info/RECORD,,
433
+ metaflow-2.18.12.data/data/share/metaflow/devtools/Makefile,sha256=TT4TCq8ALSfqYyGqDPocN5oPcZe2FqoCZxmGO1LmyCc,13760
434
+ metaflow-2.18.12.data/data/share/metaflow/devtools/Tiltfile,sha256=b6l_fjDO0wtxOkO85lFvuf3HDa6wnzHhhBG8yv1kQqk,23949
435
+ metaflow-2.18.12.data/data/share/metaflow/devtools/pick_services.sh,sha256=PGjQeDIigFHeoQ0asmYNdYDPIOdeYy1UYvkw2wdN4zg,2209
436
+ metaflow-2.18.12.dist-info/licenses/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
437
+ metaflow-2.18.12.dist-info/METADATA,sha256=O2fzkU976nnqO8o9Ufxng-zphV6LCS3qR9u-zbHSZMg,6743
438
+ metaflow-2.18.12.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
439
+ metaflow-2.18.12.dist-info/entry_points.txt,sha256=RvEq8VFlgGe_FfqGOZi0D7ze1hLD0pAtXeNyGfzc_Yc,103
440
+ metaflow-2.18.12.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
441
+ metaflow-2.18.12.dist-info/RECORD,,