nextpipe 0.6.2.dev5__tar.gz → 0.7.0__tar.gz

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 (107) hide show
  1. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/PKG-INFO +1 -1
  2. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/examples/complex-workflow-csv.md +4 -4
  3. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/examples/multifile-workflow.md +2 -3
  4. nextpipe-0.7.0/nextpipe/__about__.py +1 -0
  5. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/flow.py +144 -126
  6. nextpipe-0.6.2.dev5/nextpipe/__about__.py +0 -1
  7. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.github/workflows/lint.yml +0 -0
  8. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.github/workflows/release.yml +0 -0
  9. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.github/workflows/test.yml +0 -0
  10. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.gitignore +0 -0
  11. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.markdownlint.jsonc +0 -0
  12. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.nextmv/bump_requirements.sh +0 -0
  13. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.prettierrc.yml +0 -0
  14. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/.readthedocs.yaml +0 -0
  15. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/LICENSE.md +0 -0
  16. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/README.md +0 -0
  17. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/RELEASE.md +0 -0
  18. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/examples/basic-chained-workflow.md +0 -0
  19. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/examples/complex-workflow.md +0 -0
  20. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/examples/ensemble-workflow.md +0 -0
  21. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/examples/fanout-workflow.md +0 -0
  22. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/active-plan.png +0 -0
  23. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/api-keys.png +0 -0
  24. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/app-diagram.png +0 -0
  25. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/apps-orchestrate-full.png +0 -0
  26. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/apps-orchestrate.png +0 -0
  27. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/innovator.png +0 -0
  28. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/logo-180.png +0 -0
  29. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/new-run-orchestrate.png +0 -0
  30. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/nextmv-favicon.svg +0 -0
  31. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/plans.png +0 -0
  32. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/pushed-app.png +0 -0
  33. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/routing-ortools-pyvroom-selected.png +0 -0
  34. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/routing-selected.png +0 -0
  35. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/run-metadata.png +0 -0
  36. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/run-summary.png +0 -0
  37. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/run-workflow.png +0 -0
  38. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/secrets-collection-orchestrate.png +0 -0
  39. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/images/workflow-diagram.png +0 -0
  40. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/index.md +0 -0
  41. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/config.md +0 -0
  42. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/decorators.md +0 -0
  43. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/echo-multi.md +0 -0
  44. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/echo.md +0 -0
  45. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/flow.md +0 -0
  46. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/graph.md +0 -0
  47. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/schema.md +0 -0
  48. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/threads.md +0 -0
  49. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/uplink.md +0 -0
  50. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/reference/utils.md +0 -0
  51. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/requirements.txt +0 -0
  52. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/docs/tutorials/orchestrate-multiple-models.md +0 -0
  53. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/mkdocs.yml +0 -0
  54. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/__init__.py +0 -0
  55. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/config.py +0 -0
  56. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/constants.py +0 -0
  57. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/decorators.py +0 -0
  58. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/graph.py +0 -0
  59. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/schema.py +0 -0
  60. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/threads.py +0 -0
  61. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/uplink.py +0 -0
  62. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe/utils.py +0 -0
  63. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/nextpipe.code-workspace +0 -0
  64. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/pyproject.toml +0 -0
  65. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/.gitignore +0 -0
  66. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/README.md +0 -0
  67. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/__init__.py +0 -0
  68. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo/.gitignore +0 -0
  69. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo/README.md +0 -0
  70. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo/app.yaml +0 -0
  71. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo/main.py +0 -0
  72. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo/requirements.txt +0 -0
  73. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/.gitignore +0 -0
  74. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/README.md +0 -0
  75. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/app.yaml +0 -0
  76. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/inputs/data.csv +0 -0
  77. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/inputs/input.xlsx +0 -0
  78. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/main.py +0 -0
  79. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/apps/echo-multi/requirements.txt +0 -0
  80. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/deploy/app.yaml +0 -0
  81. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/deploy/main.py +0 -0
  82. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/deploy/requirements.txt +0 -0
  83. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/appapp.json +0 -0
  84. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/appapp.json.golden +0 -0
  85. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/appapp.py +0 -0
  86. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/chain.json +0 -0
  87. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/chain.json.golden +0 -0
  88. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/chain.py +0 -0
  89. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/complex.json +0 -0
  90. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/complex.json.golden +0 -0
  91. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/complex.py +0 -0
  92. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/fail.py +0 -0
  93. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/foreach-2-pred.json +0 -0
  94. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/foreach-2-pred.json.golden +0 -0
  95. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/foreach-2-pred.py +0 -0
  96. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/foreach.json +0 -0
  97. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/foreach.json.golden +0 -0
  98. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/foreach.py +0 -0
  99. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/inputs/data.csv +0 -0
  100. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/inputs/input.xlsx +0 -0
  101. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/pipelines/multifile.py +0 -0
  102. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/test_graph.py +0 -0
  103. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/test_integration.py +0 -0
  104. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/test_schema.py +0 -0
  105. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/test_threads.py +0 -0
  106. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/test_uplink.py +0 -0
  107. {nextpipe-0.6.2.dev5 → nextpipe-0.7.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextpipe
3
- Version: 0.6.2.dev5
3
+ Version: 0.7.0
4
4
  Summary: Framework for Decision Pipeline modeling and execution
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://nextpipe.docs.nextmv.io/en/latest/
@@ -24,7 +24,7 @@ import csv
24
24
  import json
25
25
  from typing import Any
26
26
 
27
- import nextmv.cloud
27
+ import nextmv
28
28
  import requests
29
29
 
30
30
  from nextpipe import FlowSpec, app, log, needs, repeat, step
@@ -112,9 +112,9 @@ class Workflow(FlowSpec):
112
112
  @needs(predecessors=[solve_nextroute, solve_vroom, solve_ortools])
113
113
  @step
114
114
  def pick_best(
115
- nextroute_results: list[nextmv.cloud.RunResult],
116
- vroom_result: nextmv.cloud.RunResult,
117
- ortools_result: nextmv.cloud.RunResult,
115
+ nextroute_results: list[nextmv.RunResult],
116
+ vroom_result: nextmv.RunResult,
117
+ ortools_result: nextmv.RunResult,
118
118
  ):
119
119
  """Pick the best solution based on the result value."""
120
120
 
@@ -19,7 +19,6 @@ import os
19
19
  import shutil
20
20
 
21
21
  import nextmv
22
- import nextmv.cloud
23
22
 
24
23
  from nextpipe import FlowSpec, app, log, needs, step
25
24
 
@@ -76,7 +75,7 @@ class Flow(FlowSpec):
76
75
  )
77
76
  @needs(predecessors=[transform])
78
77
  @step
79
- def solve2(result: nextmv.cloud.RunResult):
78
+ def solve2(result: nextmv.RunResult):
80
79
  """Runs another multi-file model."""
81
80
  pass
82
81
 
@@ -85,7 +84,7 @@ class Flow(FlowSpec):
85
84
  # via 'result.output'.
86
85
  @needs(predecessors=[solve2])
87
86
  @step
88
- def prepare_output(result: nextmv.cloud.RunResult):
87
+ def prepare_output(result: nextmv.RunResult):
89
88
  """Transforms the result for the next step."""
90
89
  # Extract the path to the output files.
91
90
  result_path = result.output
@@ -0,0 +1 @@
1
+ __version__ = "v0.7.0"
@@ -896,6 +896,138 @@ class Runner:
896
896
  description = f"This run was started by the nextpipe parent: {wf_app_id} (run-id: {wf_run_id})"
897
897
  return name, description
898
898
 
899
+ @staticmethod
900
+ def __prepare_app_run_args(
901
+ node: FlowNode,
902
+ inputs: list[object],
903
+ app_step: decorators.App,
904
+ ) -> tuple[dict[str, Any], nextmv.PollingOptions, bool]:
905
+ """
906
+ Prepare arguments for running an app step.
907
+
908
+ Returns
909
+ -------
910
+ tuple[dict[str, Any], nextmv.PollingOptions, bool]
911
+ A tuple of run kwargs, polling options, and whether the input is dir mode.
912
+ """
913
+
914
+ app_run_name, app_run_description = Runner.__determine_sub_run_info(app_step.app_id, node.id)
915
+
916
+ # TODO: We only support one predecessor for app steps for now. This may
917
+ # change in the future. We may want to support multiple predecessors for
918
+ # app steps. However, we need to think about how to handle the input and
919
+ # how to expose control over the input to the user.
920
+ if len(inputs) > 1:
921
+ raise Exception(f"App steps cannot have more than one predecessor, but {node.id} has {len(inputs)}")
922
+
923
+ if isinstance(inputs[0], schema.AppRunConfig):
924
+ app_run_config: schema.AppRunConfig = inputs[0]
925
+ input_data = app_run_config.input
926
+ if app_run_config.name:
927
+ app_run_name = app_run_config.name
928
+ if app_run_config.description:
929
+ app_run_description = app_run_config.description
930
+ # AppRunConfig options take precedence over decorator options.
931
+ options = app_step.options | app_run_config.get_options()
932
+ elif isinstance(inputs[0], nextmv.RunResult):
933
+ run_result: nextmv.RunResult = inputs[0]
934
+ input_data = run_result.output
935
+ options = app_step.options
936
+ else:
937
+ input_data = inputs[0]
938
+ options = app_step.options
939
+
940
+ is_dir_mode = isinstance(input_data, str) and os.path.isdir(input_data)
941
+
942
+ polling_options = copy.deepcopy(app_step.polling_options)
943
+ polling_options.initial_delay = random.uniform(0, 5)
944
+ if polling_options.stop is None:
945
+ polling_options.stop = lambda: node.cancel
946
+
947
+ run_kwargs: dict[str, Any] = {
948
+ "options": options,
949
+ "name": app_run_name,
950
+ "description": app_run_description,
951
+ }
952
+ if is_dir_mode:
953
+ run_kwargs["input_dir_path"] = input_data
954
+ else:
955
+ run_kwargs["input"] = input_data
956
+ if app_step.run_configuration is not None:
957
+ run_kwargs["configuration"] = app_step.run_configuration
958
+
959
+ return run_kwargs, polling_options, is_dir_mode
960
+
961
+ @staticmethod
962
+ def __execute_app_run(
963
+ node: FlowNode,
964
+ app: Application,
965
+ app_step: decorators.App,
966
+ client: Client,
967
+ update_dag: callable,
968
+ run_kwargs: dict[str, Any],
969
+ polling_options: nextmv.PollingOptions,
970
+ is_dir_mode: bool,
971
+ temp_dir: str,
972
+ ) -> nextmv.RunResult:
973
+ """
974
+ Execute an app run or return bypass output wrapped as a run result.
975
+ """
976
+
977
+ if app_step.bypass_result is None:
978
+ run_id = app.new_run(**run_kwargs)
979
+ console_url = f"{client.console_url}/app/{app_step.app_id}/run/{run_id}?view=details"
980
+ utils.log_internal(f"Started app step {node.id} run, find it at {console_url}")
981
+ node.run_id = run_id
982
+ update_dag() # Update node with run_id before polling
983
+
984
+ if app_step.emit_live_logs:
985
+ utils.log_internal(f"Emitting live logs for app step {node.id} ...")
986
+ app.run_logs_with_polling(
987
+ run_id=run_id,
988
+ verbose=True,
989
+ log_func=lambda msg: utils.log(message=msg.log, step_name=node.id),
990
+ )
991
+
992
+ return app.run_result_with_polling(run_id=run_id, polling_options=polling_options, output_dir_path=temp_dir)
993
+
994
+ utils.log_internal(f"Bypassing app step {node.id} run, using bypass result")
995
+
996
+ result = nextmv.RunResult(
997
+ id="bypassed-run",
998
+ name="bypassed-run",
999
+ description="This run was bypassed using a user-supplied result.",
1000
+ user_email="unavailable",
1001
+ metadata=nextmv.Metadata(
1002
+ application_id=app_step.app_id or "unavailable",
1003
+ application_instance_id=app_step.instance_id or "unavailable",
1004
+ application_version_id="unavailable",
1005
+ created_at=datetime.datetime.now(),
1006
+ duration=0.0,
1007
+ error="",
1008
+ input_size=0.0,
1009
+ output_size=0.0,
1010
+ format=nextmv.Format(
1011
+ format_input=nextmv.FormatInput(
1012
+ input_type=nextmv.InputFormat.MULTI_FILE if is_dir_mode else nextmv.InputFormat.JSON,
1013
+ ),
1014
+ ),
1015
+ status_v2=nextmv.StatusV2.succeeded,
1016
+ ),
1017
+ )
1018
+ result.output = app_step.bypass_result
1019
+
1020
+ if isinstance(app_step.bypass_result, str) and os.path.isdir(app_step.bypass_result):
1021
+ src = pathlib.Path(app_step.bypass_result)
1022
+ dst = pathlib.Path(temp_dir)
1023
+ for item in src.iterdir():
1024
+ if item.is_file():
1025
+ shutil.copy2(item, dst / item.name)
1026
+ elif item.is_dir():
1027
+ shutil.copytree(item, dst / item.name)
1028
+
1029
+ return result
1030
+
899
1031
  @staticmethod
900
1032
  def __run_step(
901
1033
  node: FlowNode,
@@ -935,72 +1067,7 @@ class Runner:
935
1067
  if node.parent.definition.is_app():
936
1068
  app_step: decorators.App = node.parent.definition.app
937
1069
 
938
- # Try to detect platform run context.
939
- app_run_name, app_run_description = Runner.__determine_sub_run_info(app_step.app_id, node.id)
940
-
941
- # Prepare the input for the app
942
- # TODO: We only support one predecessor for app steps for now. This may
943
- # change in the future. We may want to support multiple predecessors for
944
- # app steps. However, we need to think about how to handle the input and
945
- # how to expose control over the input to the user.
946
- if len(inputs) > 1:
947
- raise Exception(f"App steps cannot have more than one predecessor, but {node.id} has {len(inputs)}")
948
- if isinstance(inputs[0], schema.AppRunConfig):
949
- # If the input is AppRunConfig, unwrap it.
950
- app_run_config: schema.AppRunConfig = inputs[0]
951
- input = app_run_config.input
952
- if app_run_config.name:
953
- app_run_name = app_run_config.name
954
- if app_run_config.description:
955
- app_run_description = app_run_config.description
956
- app_run_options = app_run_config.get_options()
957
- # Merge the options from the app decorator with the options from the
958
- # AppRunConfig. AppRunConfig options take precedence.
959
- options = app_step.options | app_run_options
960
- elif isinstance(inputs[0], nextmv.RunResult):
961
- # If the input is a RunResult, we use its output as input.
962
- run_result: nextmv.RunResult = inputs[0]
963
- input = run_result.output
964
- options = app_step.options
965
- else:
966
- # If the input is not AppRunConfig, we use it directly.
967
- input = inputs[0]
968
- options = app_step.options
969
-
970
- # Detect dir mode / multi-file direct input
971
- is_dir_mode = False
972
- if isinstance(input, str) and os.path.isdir(input):
973
- is_dir_mode = True
974
-
975
- # Modify the polling options set for the step (by default or by the
976
- # user) so that the initial delay is randomized and the stopping
977
- # callback is configured as the node being cancelled if the user
978
- # doesn't want to override it.
979
- polling_options = copy.deepcopy(app_step.polling_options)
980
- delay = random.uniform(0, 5) # For lack of a better idea...
981
- polling_options.initial_delay = delay
982
- if polling_options.stop is None:
983
- polling_options.stop = lambda: node.cancel
984
-
985
- run_args = (
986
- [], # No nameless arguments
987
- { # We use the named arguments to pass the user arguments to the run function
988
- "options": options,
989
- "name": app_run_name,
990
- "description": app_run_description,
991
- },
992
- )
993
-
994
- # Prepare input argument. We need to use 'input_dir_path' when dealing with a
995
- # directory input (e.g., multi-file input).
996
- if is_dir_mode:
997
- run_args[1]["input_dir_path"] = input
998
- else:
999
- run_args[1]["input"] = input
1000
-
1001
- # Apply run configuration if given.
1002
- if app_step.run_configuration is not None:
1003
- run_args[1]["configuration"] = app_step.run_configuration
1070
+ run_kwargs, polling_options, is_dir_mode = Runner.__prepare_app_run_args(node, inputs, app_step)
1004
1071
 
1005
1072
  # Prepare the application itself.
1006
1073
  app = Application(
@@ -1015,66 +1082,17 @@ class Runner:
1015
1082
 
1016
1083
  # Run the application
1017
1084
  try:
1018
- if app_step.bypass_result is None:
1019
- # Start the run.
1020
- run_id = app.new_run(*run_args[0], **run_args[1])
1021
- console_url = f"{client.console_url}/app/{app_step.app_id}/run/{run_id}?view=details"
1022
- utils.log_internal(f"Started app step {node.id} run, find it at {console_url}")
1023
- node.run_id = run_id
1024
- update_dag() # Update the node with the run_id before we start polling for results
1025
-
1026
- # Emit live logs if requested.
1027
- if app_step.emit_live_logs:
1028
- utils.log_internal(f"Emitting live logs for app step {node.id} ...")
1029
- app.run_logs_with_polling(
1030
- run_id=run_id,
1031
- verbose=True,
1032
- log_func=lambda msg: utils.log(message=msg.log, step_name=node.id),
1033
- )
1034
-
1035
- # Wait for run to finish and get the result.
1036
- result = app.run_result_with_polling(
1037
- run_id=run_id, polling_options=polling_options, output_dir_path=temp_dir
1038
- )
1039
- else:
1040
- utils.log_internal(f"Bypassing app step {node.id} run, using bypass result")
1041
-
1042
- # We wrap the bypass result in a dummy RunResult to ensure consistent behavior.
1043
- result = nextmv.RunResult(
1044
- id="bypassed-run",
1045
- name="bypassed-run",
1046
- description="This run was bypassed using a user-supplied result.",
1047
- user_email="unavailable",
1048
- metadata=nextmv.Metadata(
1049
- application_id="unavailable",
1050
- application_instance_id="unavailable",
1051
- application_version_id="unavailable",
1052
- created_at=datetime.datetime.now(),
1053
- duration=0.0,
1054
- error="unavailable",
1055
- input_size=0.0,
1056
- output_size=0.0,
1057
- format=nextmv.Format(
1058
- format_input=nextmv.FormatInput(
1059
- input_type=nextmv.InputFormat.MULTI_FILE
1060
- if is_dir_mode
1061
- else nextmv.InputFormat.JSON,
1062
- ),
1063
- ),
1064
- status_v2=nextmv.StatusV2.succeeded,
1065
- ),
1066
- )
1067
- result.output = app_step.bypass_result
1068
-
1069
- # If the user supplied a bypass result, we need to write it to the temp dir if it's in dir mode
1070
- if isinstance(app_step.bypass_result, str) and os.path.isdir(app_step.bypass_result):
1071
- src = pathlib.Path(app_step.bypass_result)
1072
- dst = pathlib.Path(temp_dir)
1073
- for item in src.iterdir():
1074
- if item.is_file():
1075
- shutil.copy2(item, dst / item.name)
1076
- elif item.is_dir():
1077
- shutil.copytree(item, dst / item.name)
1085
+ result = Runner.__execute_app_run(
1086
+ node=node,
1087
+ app=app,
1088
+ app_step=app_step,
1089
+ client=client,
1090
+ update_dag=update_dag,
1091
+ run_kwargs=run_kwargs,
1092
+ polling_options=polling_options,
1093
+ is_dir_mode=is_dir_mode,
1094
+ temp_dir=temp_dir,
1095
+ )
1078
1096
 
1079
1097
  finally: # Make sure we clean up temp dir on failure too
1080
1098
  # If the temp dir is empty, remove it
@@ -1 +0,0 @@
1
- __version__ = "v0.6.2-dev.5"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes