ob-metaflow 2.10.7.4__py2.py3-none-any.whl → 2.10.9.1__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 (52) hide show
  1. metaflow/cards.py +2 -0
  2. metaflow/decorators.py +1 -1
  3. metaflow/metaflow_config.py +2 -0
  4. metaflow/plugins/__init__.py +4 -0
  5. metaflow/plugins/airflow/airflow_cli.py +1 -1
  6. metaflow/plugins/argo/argo_workflows_cli.py +1 -1
  7. metaflow/plugins/aws/aws_utils.py +1 -1
  8. metaflow/plugins/aws/batch/batch.py +4 -0
  9. metaflow/plugins/aws/batch/batch_cli.py +3 -0
  10. metaflow/plugins/aws/batch/batch_client.py +40 -11
  11. metaflow/plugins/aws/batch/batch_decorator.py +1 -0
  12. metaflow/plugins/aws/step_functions/step_functions.py +1 -0
  13. metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -1
  14. metaflow/plugins/azure/azure_exceptions.py +1 -1
  15. metaflow/plugins/cards/card_cli.py +413 -28
  16. metaflow/plugins/cards/card_client.py +16 -7
  17. metaflow/plugins/cards/card_creator.py +228 -0
  18. metaflow/plugins/cards/card_datastore.py +124 -26
  19. metaflow/plugins/cards/card_decorator.py +40 -86
  20. metaflow/plugins/cards/card_modules/base.html +12 -0
  21. metaflow/plugins/cards/card_modules/basic.py +74 -8
  22. metaflow/plugins/cards/card_modules/bundle.css +1 -170
  23. metaflow/plugins/cards/card_modules/card.py +65 -0
  24. metaflow/plugins/cards/card_modules/components.py +446 -81
  25. metaflow/plugins/cards/card_modules/convert_to_native_type.py +9 -3
  26. metaflow/plugins/cards/card_modules/main.js +250 -21
  27. metaflow/plugins/cards/card_modules/test_cards.py +117 -0
  28. metaflow/plugins/cards/card_resolver.py +0 -2
  29. metaflow/plugins/cards/card_server.py +361 -0
  30. metaflow/plugins/cards/component_serializer.py +506 -42
  31. metaflow/plugins/cards/exception.py +20 -1
  32. metaflow/plugins/datastores/azure_storage.py +1 -2
  33. metaflow/plugins/datastores/gs_storage.py +1 -2
  34. metaflow/plugins/datastores/s3_storage.py +2 -1
  35. metaflow/plugins/datatools/s3/s3.py +24 -11
  36. metaflow/plugins/env_escape/client.py +2 -12
  37. metaflow/plugins/env_escape/client_modules.py +18 -14
  38. metaflow/plugins/env_escape/server.py +18 -11
  39. metaflow/plugins/env_escape/utils.py +12 -0
  40. metaflow/plugins/gcp/gs_exceptions.py +1 -1
  41. metaflow/plugins/gcp/gs_utils.py +1 -1
  42. metaflow/plugins/pypi/conda_environment.py +5 -6
  43. metaflow/plugins/pypi/pip.py +2 -2
  44. metaflow/plugins/pypi/utils.py +15 -0
  45. metaflow/task.py +1 -0
  46. metaflow/version.py +1 -1
  47. {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/METADATA +1 -1
  48. {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/RECORD +52 -50
  49. {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/LICENSE +0 -0
  50. {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/WHEEL +0 -0
  51. {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/entry_points.txt +0 -0
  52. {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,17 @@
1
1
  from metaflow.client import Task
2
2
  from metaflow import JSONType, namespace
3
- from metaflow.exception import CommandException
3
+ from metaflow.util import resolve_identity
4
+ from metaflow.exception import (
5
+ CommandException,
6
+ MetaflowNotFound,
7
+ MetaflowNamespaceMismatch,
8
+ )
4
9
  import webbrowser
5
10
  import re
6
11
  from metaflow._vendor import click
7
12
  import os
8
13
  import json
14
+ import uuid
9
15
  import signal
10
16
  import random
11
17
  from contextlib import contextmanager
@@ -19,11 +25,18 @@ from .exception import (
19
25
  CardNotPresentException,
20
26
  TaskNotFoundException,
21
27
  )
28
+ import traceback
29
+ from collections import namedtuple
22
30
 
23
31
  from .card_resolver import resolve_paths_from_task, resumed_info
24
32
 
25
33
  id_func = id
26
34
 
35
+ CardRenderInfo = namedtuple(
36
+ "CardRenderInfo",
37
+ ["mode", "is_implemented", "data", "timed_out", "timeout_stack_trace"],
38
+ )
39
+
27
40
 
28
41
  def open_in_browser(card_path):
29
42
  url = "file://" + os.path.abspath(card_path)
@@ -151,8 +164,6 @@ def timeout(time):
151
164
 
152
165
  try:
153
166
  yield
154
- except TimeoutError:
155
- pass
156
167
  finally:
157
168
  # Unregister the signal so that it won't be triggered
158
169
  # if the timeout is not reached.
@@ -375,14 +386,144 @@ def card_read_options_and_arguments(func):
375
386
  return wrapper
376
387
 
377
388
 
378
- def render_card(mf_card, task, timeout_value=None):
379
- rendered_info = None
389
+ def update_card(mf_card, mode, task, data, timeout_value=None):
390
+ """
391
+ This method will be responsible for creating a card/data-update based on the `mode`.
392
+ There are three possible modes taken by this function.
393
+ - render :
394
+ - This will render the "final" card.
395
+ - This mode is passed at task completion.
396
+ - Setting this mode will call the `render` method of a MetaflowCard.
397
+ - It will result in the creation of an HTML page.
398
+ - render_runtime:
399
+ - Setting this mode will render a card during task "runtime".
400
+ - Setting this mode will call the `render_runtime` method of a MetaflowCard.
401
+ - It will result in the creation of an HTML page.
402
+ - refresh:
403
+ - Setting this mode will refresh the data update for a card.
404
+ - We support this mode because rendering a full card can be an expensive operation, but shipping tiny data updates can be cheap.
405
+ - Setting this mode will call the `refresh` method of a MetaflowCard.
406
+ - It will result in the creation of a JSON object.
407
+
408
+ Parameters
409
+ ----------
410
+ mf_card : MetaflowCard
411
+ MetaflowCard object which will be used to render the card.
412
+ mode : str
413
+ Mode of rendering the card.
414
+ task : Task
415
+ Task object which will be passed to render the card.
416
+ data : dict
417
+ object created and passed down from `current.card._get_latest_data` method.
418
+ For more information on this object's schema have a look at `current.card._get_latest_data` method.
419
+ timeout_value : int
420
+ Timeout value for rendering the card.
421
+
422
+ Returns
423
+ -------
424
+ CardRenderInfo
425
+ - NamedTuple which will contain:
426
+ - `mode`: The mode of rendering the card.
427
+ - `is_implemented`: whether the function was implemented or not.
428
+ - `data` : output from rendering the card (Can be string/dict)
429
+ - `timed_out` : whether the function timed out or not.
430
+ - `timeout_stack_trace` : stack trace of the function if it timed out.
431
+ """
432
+
433
+ def _reload_token():
434
+ if "render_seq" not in data:
435
+ return "never"
436
+
437
+ if data["render_seq"] == "final":
438
+ # final data update should always trigger a card reload to show
439
+ # the final card, hence a different token for the final update
440
+ return "final"
441
+ elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_ALWAYS:
442
+ return "render-seq-%s" % data["render_seq"]
443
+ elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_NEVER:
444
+ return "never"
445
+ elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_ONCHANGE:
446
+ return mf_card.reload_content_token(task, data)
447
+
448
+ def _add_token_html(html):
449
+ if html is None:
450
+ return None
451
+ return html.replace(mf_card.RELOAD_POLICY_TOKEN, _reload_token())
452
+
453
+ def _add_token_json(json_msg):
454
+ if json_msg is None:
455
+ return None
456
+ return {"reload_token": _reload_token(), "data": json_msg}
457
+
458
+ def _safe_call_function(func, *args, **kwargs):
459
+ """
460
+ returns (data, is_implemented)
461
+ """
462
+ try:
463
+ return func(*args, **kwargs), True
464
+ except NotImplementedError as e:
465
+ return None, False
466
+
467
+ def _call():
468
+ if mode == "render":
469
+ setattr(
470
+ mf_card.__class__,
471
+ "runtime_data",
472
+ property(fget=lambda _, data=data: data),
473
+ )
474
+ output = _add_token_html(mf_card.render(task))
475
+ return CardRenderInfo(
476
+ mode=mode,
477
+ is_implemented=True,
478
+ data=output,
479
+ timed_out=False,
480
+ timeout_stack_trace=None,
481
+ )
482
+
483
+ elif mode == "render_runtime":
484
+ # Since many cards created by metaflow users may not have implemented a
485
+ # `render_time` / `refresh` methods, it can result in an exception and thereby
486
+ # creation of error cards (especially for the `render_runtime` method). So instead
487
+ # we will catch the NotImplementedError and return None if users have not implemented it.
488
+ # If there any any other exception from the user code, it should be bubbled to the top level.
489
+ output, is_implemented = _safe_call_function(
490
+ mf_card.render_runtime, task, data
491
+ )
492
+ return CardRenderInfo(
493
+ mode=mode,
494
+ is_implemented=is_implemented,
495
+ data=_add_token_html(output),
496
+ timed_out=False,
497
+ timeout_stack_trace=None,
498
+ )
499
+
500
+ elif mode == "refresh":
501
+ output, is_implemented = _safe_call_function(mf_card.refresh, task, data)
502
+ return CardRenderInfo(
503
+ mode=mode,
504
+ is_implemented=is_implemented,
505
+ data=_add_token_json(output),
506
+ timed_out=False,
507
+ timeout_stack_trace=None,
508
+ )
509
+
510
+ render_info = None
380
511
  if timeout_value is None or timeout_value < 0:
381
- rendered_info = mf_card.render(task)
512
+ return _call()
382
513
  else:
383
- with timeout(timeout_value):
384
- rendered_info = mf_card.render(task)
385
- return rendered_info
514
+ try:
515
+ with timeout(timeout_value):
516
+ render_info = _call()
517
+ except TimeoutError:
518
+ stack_trace = traceback.format_exc()
519
+ return CardRenderInfo(
520
+ mode=mode,
521
+ is_implemented=True,
522
+ data=None,
523
+ timed_out=True,
524
+ timeout_stack_trace=stack_trace,
525
+ )
526
+ return render_info
386
527
 
387
528
 
388
529
  @card.command(help="create a HTML card")
@@ -414,29 +555,64 @@ def render_card(mf_card, task, timeout_value=None):
414
555
  is_flag=True,
415
556
  help="Upon failing to render a card, render a card holding the stack trace",
416
557
  )
558
+ @click.option(
559
+ "--id",
560
+ default=None,
561
+ show_default=True,
562
+ type=str,
563
+ help="ID of the card",
564
+ )
417
565
  @click.option(
418
566
  "--component-file",
419
567
  default=None,
420
568
  show_default=True,
421
569
  type=str,
422
- help="JSON File with Pre-rendered components.(internal)",
570
+ help="JSON File with pre-rendered components. (internal)",
423
571
  )
424
572
  @click.option(
425
- "--id",
573
+ "--mode",
574
+ default="render",
575
+ show_default=True,
576
+ type=click.Choice(["render", "render_runtime", "refresh"]),
577
+ help="Rendering mode. (internal)",
578
+ )
579
+ @click.option(
580
+ "--data-file",
426
581
  default=None,
427
582
  show_default=True,
428
583
  type=str,
429
- help="ID of the card",
584
+ hidden=True,
585
+ help="JSON file containing data to be updated. (internal)",
586
+ )
587
+ @click.option(
588
+ "--card-uuid",
589
+ default=None,
590
+ show_default=True,
591
+ type=str,
592
+ hidden=True,
593
+ help="Card UUID. (internal)",
594
+ )
595
+ @click.option(
596
+ "--delete-input-files",
597
+ default=False,
598
+ is_flag=True,
599
+ show_default=True,
600
+ hidden=True,
601
+ help="Delete data-file and component-file after reading. (internal)",
430
602
  )
431
603
  @click.pass_context
432
604
  def create(
433
605
  ctx,
434
606
  pathspec,
607
+ mode=None,
435
608
  type=None,
436
609
  options=None,
437
610
  timeout=None,
438
611
  component_file=None,
612
+ data_file=None,
439
613
  render_error_card=False,
614
+ card_uuid=None,
615
+ delete_input_files=None,
440
616
  id=None,
441
617
  ):
442
618
  card_id = id
@@ -452,11 +628,26 @@ def create(
452
628
 
453
629
  graph_dict, _ = ctx.obj.graph.output_steps()
454
630
 
631
+ if card_uuid is None:
632
+ card_uuid = str(uuid.uuid4()).replace("-", "")
633
+
455
634
  # Components are rendered in a Step and added via `current.card.append` are added here.
456
635
  component_arr = []
457
636
  if component_file is not None:
458
637
  with open(component_file, "r") as f:
459
638
  component_arr = json.load(f)
639
+ # Component data used in card runtime is passed in as temporary files which can be deleted after use
640
+ if delete_input_files:
641
+ os.remove(component_file)
642
+
643
+ # Load data to be refreshed for runtime cards
644
+ data = {}
645
+ if data_file is not None:
646
+ with open(data_file, "r") as f:
647
+ data = json.load(f)
648
+ # data is passed in as temporary files which can be deleted after use
649
+ if delete_input_files:
650
+ os.remove(data_file)
460
651
 
461
652
  task = Task(full_pathspec)
462
653
  from metaflow.plugins import CARDS
@@ -498,23 +689,88 @@ def create(
498
689
  else:
499
690
  raise IncorrectCardArgsException(type, options)
500
691
 
692
+ rendered_content = None
501
693
  if mf_card:
502
694
  try:
503
- rendered_info = render_card(mf_card, task, timeout_value=timeout)
695
+ rendered_info = update_card(
696
+ mf_card, mode, task, data, timeout_value=timeout
697
+ )
698
+ rendered_content = rendered_info.data
504
699
  except:
700
+ rendered_info = CardRenderInfo(
701
+ mode=mode,
702
+ is_implemented=True,
703
+ data=None,
704
+ timed_out=False,
705
+ timeout_stack_trace=None,
706
+ )
505
707
  if render_error_card:
506
708
  error_stack_trace = str(UnrenderableCardException(type, options))
507
709
  else:
508
710
  raise UnrenderableCardException(type, options)
509
- #
510
-
511
- if error_stack_trace is not None:
512
- rendered_info = error_card().render(task, stack_trace=error_stack_trace)
513
711
 
514
- if rendered_info is None and render_error_card:
515
- rendered_info = error_card().render(
516
- task, stack_trace="No information rendered From card of type %s" % type
712
+ # In the entire card rendering process, there are a few cases we want to handle:
713
+ # - [mode == "render"]
714
+ # 1. Card is rendered successfully (We store it in the datastore as a HTML file)
715
+ # 2. Card is not rendered successfully and we have --save-error-card flag set to True
716
+ # (We store it in the datastore as a HTML file with stack trace)
717
+ # 3. Card render timed-out and we have --save-error-card flag set to True
718
+ # (We store it in the datastore as a HTML file with stack trace)
719
+ # 4. `render` returns nothing and we have --save-error-card flag set to True.
720
+ # (We store it in the datastore as a HTML file with some message saying you returned nothing)
721
+ # - [mode == "render_runtime"]
722
+ # 1. Card is rendered successfully (We store it in the datastore as a HTML file)
723
+ # 2. `render_runtime` is not implemented but gets called and we have --save-error-card flag set to True.
724
+ # (We store it in the datastore as a HTML file with some message saying the card should not be a runtime card if this method is not Implemented)
725
+ # 3. `render_runtime` is implemented and raises an exception and we have --save-error-card flag set to True.
726
+ # (We store it in the datastore as a HTML file with stack trace)
727
+ # 4. `render_runtime` is implemented but returns nothing and we have --save-error-card flag set to True.
728
+ # (We store it in the datastore as a HTML file with some message saying you returned nothing)
729
+ # 5. `render_runtime` is implemented but times out and we have --save-error-card flag set to True.
730
+ # (We store it in the datastore as a HTML file with stack trace)
731
+ # - [mode == "refresh"]
732
+ # 1. Data update is created successfully (We store it in the datastore as a JSON file)
733
+ # 2. `refresh` is not implemented. (We do nothing. Don't store anything.)
734
+ # 3. `refresh` is implemented but it raises an exception. (We do nothing. Don't store anything.)
735
+ # 4. `refresh` is implemented but it times out. (We do nothing. Don't store anything.)
736
+
737
+ if error_stack_trace is not None and mode != "refresh":
738
+ rendered_content = error_card().render(task, stack_trace=error_stack_trace)
739
+
740
+ if (
741
+ rendered_info.is_implemented
742
+ and rendered_info.timed_out
743
+ and mode != "refresh"
744
+ and render_error_card
745
+ ):
746
+ timeout_stack_trace = (
747
+ "\nCard rendering timed out after %s seconds. "
748
+ "To increase the timeout duration for card rendering, please set the `timeout` parameter in the @card decorator. "
749
+ "\nStack Trace : \n%s"
750
+ ) % (timeout, rendered_info.timeout_stack_trace)
751
+ rendered_content = error_card().render(
752
+ task,
753
+ stack_trace=timeout_stack_trace,
754
+ )
755
+ elif (
756
+ rendered_info.is_implemented
757
+ and rendered_info.data is None
758
+ and render_error_card
759
+ and mode != "refresh"
760
+ ):
761
+ rendered_content = error_card().render(
762
+ task, stack_trace="No information rendered from card of type %s" % type
517
763
  )
764
+ elif (
765
+ not rendered_info.is_implemented
766
+ and render_error_card
767
+ and mode == "render_runtime"
768
+ ):
769
+ message = (
770
+ "Card of type %s is a runtime time card with no `render_runtime` implemented. "
771
+ "Please implement `render_runtime` method to allow rendering this card at runtime."
772
+ ) % type
773
+ rendered_content = error_card().render(task, stack_trace=message)
518
774
 
519
775
  # todo : should we save native type for error card or error type ?
520
776
  if type is not None and re.match(CARD_ID_PATTERN, type) is not None:
@@ -531,13 +787,21 @@ def create(
531
787
  )
532
788
  card_id = None
533
789
 
534
- if rendered_info is not None:
535
- card_info = card_datastore.save_card(save_type, rendered_info, card_id=card_id)
536
- ctx.obj.echo(
537
- "Card created with type: %s and hash: %s"
538
- % (card_info.type, card_info.hash[:NUM_SHORT_HASH_CHARS]),
539
- fg="green",
540
- )
790
+ if rendered_content is not None:
791
+ if mode == "refresh":
792
+ card_datastore.save_data(
793
+ card_uuid, save_type, rendered_content, card_id=card_id
794
+ )
795
+ ctx.obj.echo("Data updated", fg="green")
796
+ else:
797
+ card_info = card_datastore.save_card(
798
+ card_uuid, save_type, rendered_content, card_id=card_id
799
+ )
800
+ ctx.obj.echo(
801
+ "Card created with type: %s and hash: %s"
802
+ % (card_info.type, card_info.hash[:NUM_SHORT_HASH_CHARS]),
803
+ fg="green",
804
+ )
541
805
 
542
806
 
543
807
  @card.command()
@@ -655,7 +919,6 @@ def list(
655
919
  as_json=False,
656
920
  file=None,
657
921
  ):
658
-
659
922
  card_id = id
660
923
  if pathspec is None:
661
924
  list_many_cards(
@@ -687,3 +950,125 @@ def list(
687
950
  show_list_as_json=as_json,
688
951
  file=file,
689
952
  )
953
+
954
+
955
+ @card.command(help="Run local card viewer server")
956
+ @click.option(
957
+ "--run-id",
958
+ default=None,
959
+ show_default=True,
960
+ type=str,
961
+ help="Run ID of the flow",
962
+ )
963
+ @click.option(
964
+ "--port",
965
+ default=8324,
966
+ show_default=True,
967
+ type=int,
968
+ help="Port on which Metaflow card viewer server will run",
969
+ )
970
+ @click.option(
971
+ "--namespace",
972
+ "user_namespace",
973
+ default=None,
974
+ show_default=True,
975
+ type=str,
976
+ help="Namespace of the flow",
977
+ )
978
+ @click.option(
979
+ "--poll-interval",
980
+ default=5,
981
+ show_default=True,
982
+ type=int,
983
+ help="Polling interval of the card viewer server.",
984
+ )
985
+ @click.option(
986
+ "--max-cards",
987
+ default=30,
988
+ show_default=True,
989
+ type=int,
990
+ help="Maximum number of cards to be shown at any time by the card viewer server",
991
+ )
992
+ @click.pass_context
993
+ def server(ctx, run_id, port, user_namespace, poll_interval, max_cards):
994
+ from .card_server import create_card_server, CardServerOptions
995
+
996
+ user_namespace = resolve_identity() if user_namespace is None else user_namespace
997
+ run, follow_new_runs, _status_message = _get_run_object(
998
+ ctx.obj, run_id, user_namespace
999
+ )
1000
+ if _status_message is not None:
1001
+ ctx.obj.echo(_status_message, fg="red")
1002
+ options = CardServerOptions(
1003
+ flow_name=ctx.obj.flow.name,
1004
+ run_object=run,
1005
+ only_running=False,
1006
+ follow_resumed=False,
1007
+ flow_datastore=ctx.obj.flow_datastore,
1008
+ max_cards=max_cards,
1009
+ follow_new_runs=follow_new_runs,
1010
+ poll_interval=poll_interval,
1011
+ )
1012
+ create_card_server(options, port, ctx.obj)
1013
+
1014
+
1015
+ def _get_run_from_cli_set_runid(obj, run_id):
1016
+ # This run-id will be set from the command line args.
1017
+ # So if we hit a MetaflowNotFound exception / Namespace mismatch then
1018
+ # we should raise an exception
1019
+ from metaflow import Run
1020
+
1021
+ flow_name = obj.flow.name
1022
+ if len(run_id.split("/")) > 1:
1023
+ raise CommandException(
1024
+ "run_id should NOT be of the form: `<flowname>/<runid>`. Please provide only run-id"
1025
+ )
1026
+ try:
1027
+ pathspec = "%s/%s" % (flow_name, run_id)
1028
+ # Since we are looking at all namespaces,
1029
+ # we will not
1030
+ namespace(None)
1031
+ return Run(pathspec)
1032
+ except MetaflowNotFound:
1033
+ raise CommandException("No run (%s) found for *%s*." % (run_id, flow_name))
1034
+
1035
+
1036
+ def _get_run_object(obj, run_id, user_namespace):
1037
+ from metaflow import Flow
1038
+
1039
+ follow_new_runs = True
1040
+ flow_name = obj.flow.name
1041
+
1042
+ if run_id is not None:
1043
+ follow_new_runs = False
1044
+ run = _get_run_from_cli_set_runid(obj, run_id)
1045
+ obj.echo("Using run-id %s" % run.pathspec, fg="blue", bold=False)
1046
+ return run, follow_new_runs, None
1047
+
1048
+ _msg = "Searching for runs in namespace: %s" % user_namespace
1049
+ obj.echo(_msg, fg="blue", bold=False)
1050
+
1051
+ try:
1052
+ namespace(user_namespace)
1053
+ flow = Flow(pathspec=flow_name)
1054
+ run = flow.latest_run
1055
+ except MetaflowNotFound:
1056
+ # When we have no runs found for the Flow, we need to ensure that
1057
+ # if the `follow_new_runs` is set to True; If `follow_new_runs` is set to True then
1058
+ # we don't raise the Exception and instead we return None and let the
1059
+ # background Thread wait on the Retrieving the run object.
1060
+ _status_msg = "No run found for *%s*." % flow_name
1061
+ return None, follow_new_runs, _status_msg
1062
+
1063
+ except MetaflowNamespaceMismatch:
1064
+ _status_msg = (
1065
+ "No run found for *%s* in namespace *%s*. You can switch the namespace using --namespace"
1066
+ % (
1067
+ flow_name,
1068
+ user_namespace,
1069
+ )
1070
+ )
1071
+ return None, follow_new_runs, _status_msg
1072
+
1073
+ obj.echo("Using run-id %s" % run.pathspec, fg="blue", bold=False)
1074
+ return run, follow_new_runs, None
@@ -2,10 +2,10 @@ from typing import Optional, Union, TYPE_CHECKING
2
2
  from metaflow.datastore import FlowDataStore
3
3
  from metaflow.metaflow_config import CARD_SUFFIX
4
4
  from .card_resolver import resolve_paths_from_task, resumed_info
5
- from .card_datastore import CardDatastore
5
+ from .card_datastore import CardDatastore, CardNameSuffix
6
6
  from .exception import (
7
7
  UnresolvableDatastoreException,
8
- IncorrectArguementException,
8
+ IncorrectArgumentException,
9
9
  IncorrectPathspecException,
10
10
  )
11
11
  import os
@@ -57,6 +57,15 @@ class Card:
57
57
  # Tempfile to open stuff in browser
58
58
  self._temp_file = None
59
59
 
60
+ def get_data(self) -> Optional[dict]:
61
+ # currently an internal method to retrieve a card's data.
62
+ data_paths = self._card_ds.extract_data_paths(
63
+ card_type=self.type, card_hash=self.hash, card_id=self._card_id
64
+ )
65
+ if len(data_paths) == 0:
66
+ return None
67
+ return self._card_ds.get_card_data(data_paths[0])
68
+
60
69
  def get(self) -> str:
61
70
  """
62
71
  Retrieves the HTML contents of the card from the
@@ -150,12 +159,12 @@ class CardContainer:
150
159
  ```
151
160
  """
152
161
 
153
- def __init__(self, card_paths, card_ds, from_resumed=False, origin_pathspec=None):
162
+ def __init__(self, card_paths, card_ds, origin_pathspec=None):
154
163
  self._card_paths = card_paths
155
164
  self._card_ds = card_ds
156
165
  self._current = 0
157
166
  self._high = len(card_paths)
158
- self.from_resumed = from_resumed
167
+ self.from_resumed = origin_pathspec is not None
159
168
  self.origin_pathspec = origin_pathspec
160
169
 
161
170
  def __len__(self):
@@ -172,7 +181,7 @@ class CardContainer:
172
181
  if index >= self._high:
173
182
  raise IndexError
174
183
  path = self._card_paths[index]
175
- card_info = self._card_ds.card_info_from_path(path)
184
+ card_info = self._card_ds.info_from_path(path, suffix=CardNameSuffix.CARD)
176
185
  # todo : find card creation date and put it in client.
177
186
  return Card(
178
187
  self._card_ds,
@@ -250,8 +259,9 @@ def get_cards(
250
259
  task = Task(task_str)
251
260
  elif not isinstance(task, Task):
252
261
  # Exception that the task argument should be of form `Task` or `str`
253
- raise IncorrectArguementException(_TYPE(task))
262
+ raise IncorrectArgumentException(_TYPE(task))
254
263
 
264
+ origin_taskpathspec = None
255
265
  if follow_resumed:
256
266
  origin_taskpathspec = resumed_info(task)
257
267
  if origin_taskpathspec:
@@ -263,7 +273,6 @@ def get_cards(
263
273
  return CardContainer(
264
274
  card_paths,
265
275
  card_ds,
266
- from_resumed=origin_taskpathspec is not None,
267
276
  origin_pathspec=origin_taskpathspec,
268
277
  )
269
278