nextpipe 0.7.2__tar.gz → 0.7.3.dev0__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 (71) hide show
  1. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/PKG-INFO +1 -1
  2. nextpipe-0.7.3.dev0/nextpipe/__about__.py +1 -0
  3. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/decorators.py +12 -3
  4. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/flow.py +24 -10
  5. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/schema.py +54 -1
  6. nextpipe-0.7.3.dev0/tests/test_flow.py +104 -0
  7. nextpipe-0.7.3.dev0/tests/test_schema.py +69 -0
  8. nextpipe-0.7.2/nextpipe/__about__.py +0 -1
  9. nextpipe-0.7.2/tests/test_schema.py +0 -27
  10. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.github/workflows/lint.yml +0 -0
  11. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.github/workflows/release.yml +0 -0
  12. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.github/workflows/test.yml +0 -0
  13. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.gitignore +0 -0
  14. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.nextmv/bump_requirements.sh +0 -0
  15. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.prettierrc.yml +0 -0
  16. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/.python-version +0 -0
  17. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/LICENSE.md +0 -0
  18. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/README.md +0 -0
  19. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/RELEASE.md +0 -0
  20. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/__init__.py +0 -0
  21. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/config.py +0 -0
  22. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/constants.py +0 -0
  23. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/graph.py +0 -0
  24. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/threads.py +0 -0
  25. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/uplink.py +0 -0
  26. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe/utils.py +0 -0
  27. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/nextpipe.code-workspace +0 -0
  28. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/pyproject.toml +0 -0
  29. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/.gitignore +0 -0
  30. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/README.md +0 -0
  31. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/__init__.py +0 -0
  32. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo/.gitignore +0 -0
  33. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo/README.md +0 -0
  34. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo/app.yaml +0 -0
  35. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo/main.py +0 -0
  36. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo/requirements.txt +0 -0
  37. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/.gitignore +0 -0
  38. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/README.md +0 -0
  39. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/app.yaml +0 -0
  40. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/inputs/data.csv +0 -0
  41. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/inputs/input.xlsx +0 -0
  42. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/main.py +0 -0
  43. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/apps/echo-multi/requirements.txt +0 -0
  44. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/deploy/app.yaml +0 -0
  45. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/deploy/main.py +0 -0
  46. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/deploy/requirements.txt +0 -0
  47. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/appapp.json +0 -0
  48. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/appapp.json.golden +0 -0
  49. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/appapp.py +0 -0
  50. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/chain.json +0 -0
  51. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/chain.json.golden +0 -0
  52. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/chain.py +0 -0
  53. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/complex.json +0 -0
  54. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/complex.json.golden +0 -0
  55. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/complex.py +0 -0
  56. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/fail.py +0 -0
  57. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/foreach-2-pred.json +0 -0
  58. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/foreach-2-pred.json.golden +0 -0
  59. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/foreach-2-pred.py +0 -0
  60. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/foreach.json +0 -0
  61. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/foreach.json.golden +0 -0
  62. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/foreach.py +0 -0
  63. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/inputs/data.csv +0 -0
  64. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/inputs/input.xlsx +0 -0
  65. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/pipelines/multifile.py +0 -0
  66. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/test_graph.py +0 -0
  67. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/test_integration.py +0 -0
  68. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/test_threads.py +0 -0
  69. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/test_uplink.py +0 -0
  70. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/tests/test_version.py +0 -0
  71. {nextpipe-0.7.2 → nextpipe-0.7.3.dev0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nextpipe
3
- Version: 0.7.2
3
+ Version: 0.7.3-dev.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://docs.nextmv.io/nextpipe
@@ -0,0 +1 @@
1
+ __version__ = "v0.7.3-dev.0"
@@ -798,12 +798,16 @@ class App:
798
798
  The ID of the Nextmv Application to run.
799
799
  instance_id : str, optional
800
800
  The ID of the instance to run. Default is defined by the app on Platform.
801
+ A predecessor step may override this per run by returning an `AppRunConfig`
802
+ carrying its own `instance_id`.
801
803
  options : dict[str, Any], optional
802
804
  The options to pass to the application, by default None.
803
805
  full_result : bool, optional
804
806
  Whether to return the full result including metadata, by default False.
805
807
  run_configuration : nextmv.RunConfiguration, optional
806
- The configuration to apply when running the app, by default None.
808
+ The configuration to apply when running the app, by default None. A
809
+ predecessor step may override this per run by returning an
810
+ `AppRunConfig` carrying its own `run_configuration`.
807
811
  polling_options : Optional[nextmv.PollingOptions], optional
808
812
  Options for polling for the results of the app run, by default _DEFAULT_POLLING_OPTIONS.
809
813
  bypass_result : typing.Any, optional
@@ -884,7 +888,9 @@ def app(
884
888
  app_id : str
885
889
  The ID of the application to run.
886
890
  instance_id : str
887
- The ID of the instance to run. Default is defined by the app on Platform.
891
+ The ID of the instance to run. Default is defined by the app on Platform. A
892
+ predecessor step may override this per run by returning an `AppRunConfig`
893
+ carrying its own `instance_id`.
888
894
  options : dict[str, Any]
889
895
  The options to pass to the application. This is a dictionary of
890
896
  parameter names and values. The values must be JSON serializable.
@@ -894,7 +900,10 @@ def app(
894
900
  this is set to `False`, only the output of the application will be
895
901
  returned.
896
902
  run_configuration : nextmv.RunConfiguration
897
- The configuration to apply when running the app.
903
+ The configuration to apply when running the app. A predecessor step may
904
+ override this per run by returning an `AppRunConfig` carrying its own
905
+ `run_configuration`. In that case, the configuration of the `AppRunConfig`
906
+ replaces this one as a whole, i.e., no fields are inherited from it.
898
907
  polling_options : Optional[nextmv.PollingOptions]
899
908
  Options for polling for the results of the app run. This is used to
900
909
  configure the polling behavior, such as the timeout and backoff
@@ -901,14 +901,15 @@ class Runner:
901
901
  node: FlowNode,
902
902
  inputs: list[object],
903
903
  app_step: decorators.App,
904
- ) -> tuple[dict[str, Any], nextmv.PollingOptions, bool]:
904
+ ) -> tuple[dict[str, Any], nextmv.PollingOptions, bool, str]:
905
905
  """
906
906
  Prepare arguments for running an app step.
907
907
 
908
908
  Returns
909
909
  -------
910
- tuple[dict[str, Any], nextmv.PollingOptions, bool]
911
- A tuple of run kwargs, polling options, and whether the input is dir mode.
910
+ tuple[dict[str, Any], nextmv.PollingOptions, bool, str]
911
+ A tuple of run kwargs, polling options, whether the input is dir mode, and
912
+ the ID of the instance to run.
912
913
  """
913
914
 
914
915
  app_run_name, app_run_description = Runner.__determine_sub_run_info(app_step.app_id, node.id)
@@ -920,6 +921,9 @@ class Runner:
920
921
  if len(inputs) > 1:
921
922
  raise Exception(f"App steps cannot have more than one predecessor, but {node.id} has {len(inputs)}")
922
923
 
924
+ run_configuration = app_step.run_configuration
925
+ instance_id = app_step.instance_id
926
+
923
927
  if isinstance(inputs[0], schema.AppRunConfig):
924
928
  app_run_config: schema.AppRunConfig = inputs[0]
925
929
  input_data = app_run_config.input
@@ -929,6 +933,12 @@ class Runner:
929
933
  app_run_description = app_run_config.description
930
934
  # AppRunConfig options take precedence over decorator options.
931
935
  options = app_step.options | app_run_config.get_options()
936
+ # An AppRunConfig run configuration replaces the decorator one as a whole.
937
+ if app_run_config.run_configuration is not None:
938
+ run_configuration = app_run_config.run_configuration
939
+ # Same for the instance to run on.
940
+ if app_run_config.instance_id:
941
+ instance_id = app_run_config.instance_id
932
942
  elif isinstance(inputs[0], nextmv.RunResult):
933
943
  run_result: nextmv.RunResult = inputs[0]
934
944
  input_data = run_result.output
@@ -953,10 +963,10 @@ class Runner:
953
963
  run_kwargs["input_dir_path"] = input_data
954
964
  else:
955
965
  run_kwargs["input"] = input_data
956
- if app_step.run_configuration is not None:
957
- run_kwargs["configuration"] = app_step.run_configuration
966
+ if run_configuration is not None:
967
+ run_kwargs["configuration"] = run_configuration
958
968
 
959
- return run_kwargs, polling_options, is_dir_mode
969
+ return run_kwargs, polling_options, is_dir_mode, instance_id
960
970
 
961
971
  @staticmethod
962
972
  def __execute_app_run(
@@ -969,6 +979,7 @@ class Runner:
969
979
  polling_options: nextmv.PollingOptions,
970
980
  is_dir_mode: bool,
971
981
  temp_dir: str,
982
+ instance_id: str,
972
983
  ) -> nextmv.RunResult:
973
984
  """
974
985
  Execute an app run or return bypass output wrapped as a run result.
@@ -1000,7 +1011,7 @@ class Runner:
1000
1011
  user_email="unavailable",
1001
1012
  metadata=nextmv.Metadata(
1002
1013
  application_id=app_step.app_id or "unavailable",
1003
- application_instance_id=app_step.instance_id or "unavailable",
1014
+ application_instance_id=instance_id or "unavailable",
1004
1015
  application_version_id="unavailable",
1005
1016
  created_at=datetime.datetime.now(),
1006
1017
  duration=0.0,
@@ -1067,15 +1078,17 @@ class Runner:
1067
1078
  if node.parent.definition.is_app():
1068
1079
  app_step: decorators.App = node.parent.definition.app
1069
1080
 
1070
- run_kwargs, polling_options, is_dir_mode = Runner.__prepare_app_run_args(node, inputs, app_step)
1081
+ run_kwargs, polling_options, is_dir_mode, instance_id = Runner.__prepare_app_run_args(
1082
+ node, inputs, app_step
1083
+ )
1071
1084
 
1072
1085
  # Prepare the application itself.
1073
1086
  app = Application(
1074
1087
  client=client,
1075
1088
  id=app_step.app_id,
1076
1089
  )
1077
- if app_step.instance_id is not None and app_step.instance_id != "":
1078
- app.default_instance_id = app_step.instance_id
1090
+ if instance_id is not None and instance_id != "":
1091
+ app.default_instance_id = instance_id
1079
1092
 
1080
1093
  # We always supply an output directory path in case of implicit multi-file output
1081
1094
  temp_dir = tempfile.mkdtemp(prefix="nextpipe_output_")
@@ -1092,6 +1105,7 @@ class Runner:
1092
1105
  polling_options=polling_options,
1093
1106
  is_dir_mode=is_dir_mode,
1094
1107
  temp_dir=temp_dir,
1108
+ instance_id=instance_id,
1095
1109
  )
1096
1110
 
1097
1111
  finally: # Make sure we clean up temp dir on failure too
@@ -14,11 +14,31 @@ AppRunConfig
14
14
  from dataclasses import dataclass, field
15
15
  from typing import Any
16
16
 
17
- from dataclasses_json import dataclass_json
17
+ import nextmv
18
+ from dataclasses_json import config, dataclass_json
18
19
 
19
20
  from . import utils
20
21
 
21
22
 
23
+ def _encode_run_configuration(
24
+ run_configuration: nextmv.RunConfiguration | None,
25
+ ) -> dict[str, Any] | None:
26
+ """Encode a run configuration for JSON serialization."""
27
+
28
+ return run_configuration.to_dict() if run_configuration is not None else None
29
+
30
+
31
+ def _decode_run_configuration(
32
+ run_configuration: dict[str, Any] | nextmv.RunConfiguration | None,
33
+ ) -> nextmv.RunConfiguration | None:
34
+ """Decode a run configuration from its JSON representation."""
35
+
36
+ if run_configuration is None or isinstance(run_configuration, nextmv.RunConfiguration):
37
+ return run_configuration
38
+
39
+ return nextmv.RunConfiguration.from_dict(run_configuration)
40
+
41
+
22
42
  @dataclass_json
23
43
  @dataclass
24
44
  class AppOption:
@@ -79,6 +99,16 @@ class AppRunConfig:
79
99
  `AppOption` instances, or, simply as a dictionary of key-value pairs.
80
100
  name : str, optional
81
101
  Name for the run, by default None.
102
+ description : str, optional
103
+ Description for the run, by default None.
104
+ run_configuration : nextmv.RunConfiguration, optional
105
+ The configuration to apply when running the app, by default None. If given, it
106
+ replaces the `run_configuration` of the `app` decorator for this run. Note that
107
+ it replaces it as a whole, i.e., no fields are inherited from the decorator's
108
+ configuration.
109
+ instance_id : str, optional
110
+ The ID of the instance to run, by default None. If given, it replaces the
111
+ `instance_id` of the `app` decorator for this run.
82
112
 
83
113
  Examples
84
114
  --------
@@ -88,6 +118,17 @@ class AppRunConfig:
88
118
  ... options={"threads": 4},
89
119
  ... name="my-run"
90
120
  ... )
121
+
122
+ The run configuration and instance can be chosen per run, e.g., to pick an
123
+ execution class based on the size of the scenario at hand.
124
+
125
+ >>> import nextmv
126
+ >>> from nextpipe import AppRunConfig
127
+ >>> config = AppRunConfig(
128
+ ... input={"data": [1, 2, 3]},
129
+ ... run_configuration=nextmv.RunConfiguration(execution_class="8c16gb12h"),
130
+ ... instance_id="tuned-for-large",
131
+ ... )
91
132
  """
92
133
 
93
134
  input: dict[str, Any] | str | Any
@@ -100,6 +141,18 @@ class AppRunConfig:
100
141
  """Name for the run."""
101
142
  description: str | None = None
102
143
  """Description for the run."""
144
+ run_configuration: nextmv.RunConfiguration | None = field(
145
+ default=None,
146
+ metadata=config(
147
+ encoder=_encode_run_configuration,
148
+ decoder=_decode_run_configuration,
149
+ ),
150
+ )
151
+ """The configuration to apply when running the app. Replaces the `run_configuration`
152
+ of the `app` decorator as a whole, if given."""
153
+ instance_id: str | None = None
154
+ """The ID of the instance to run. Replaces the `instance_id` of the `app` decorator,
155
+ if given."""
103
156
 
104
157
  def get_options(self) -> dict[str, Any]:
105
158
  """
@@ -0,0 +1,104 @@
1
+ import os
2
+ import sys
3
+ import unittest
4
+
5
+ import nextmv
6
+
7
+ from nextpipe import AppRunConfig, decorators
8
+ from nextpipe.flow import Runner
9
+
10
+ # Add the parent directory to the sys.path to allow imports from the main package. This
11
+ # is meant to help VS Code testing features.
12
+ sys.path.append(os.path.dirname(sys.path[0]))
13
+
14
+
15
+ class _FakeNode:
16
+ """Minimal stand-in for a FlowNode, providing what run arg preparation needs."""
17
+
18
+ def __init__(self, id: str = "solve_0"):
19
+ self.id = id
20
+ self.cancel = False
21
+
22
+
23
+ def _prepare(inputs: list[object], app_step: decorators.App) -> tuple[dict, str]:
24
+ """Prepare the run kwargs and instance ID for the given inputs and app step."""
25
+
26
+ run_kwargs, _, _, instance_id = Runner._Runner__prepare_app_run_args(_FakeNode(), inputs, app_step)
27
+ return run_kwargs, instance_id
28
+
29
+
30
+ class TestPrepareAppRunArgs(unittest.TestCase):
31
+ def test_no_run_configuration(self):
32
+ app_step = decorators.App(app_id="echo")
33
+ run_kwargs, _ = _prepare([{"data": [1, 2, 3]}], app_step)
34
+ self.assertNotIn("configuration", run_kwargs)
35
+
36
+ def test_decorator_run_configuration(self):
37
+ app_step = decorators.App(
38
+ app_id="echo",
39
+ run_configuration=nextmv.RunConfiguration(execution_class="4c8gb12h"),
40
+ )
41
+ run_kwargs, _ = _prepare([{"data": [1, 2, 3]}], app_step)
42
+ self.assertEqual(run_kwargs["configuration"].execution_class, "4c8gb12h")
43
+
44
+ def test_app_run_config_run_configuration(self):
45
+ app_step = decorators.App(app_id="echo")
46
+ app_run_config = AppRunConfig(
47
+ input={"data": [1, 2, 3]},
48
+ run_configuration=nextmv.RunConfiguration(execution_class="8c16gb12h"),
49
+ )
50
+ run_kwargs, _ = _prepare([app_run_config], app_step)
51
+ self.assertEqual(run_kwargs["configuration"].execution_class, "8c16gb12h")
52
+
53
+ def test_app_run_config_run_configuration_replaces_decorator(self):
54
+ """The run configuration of an AppRunConfig replaces the decorator one as a whole."""
55
+
56
+ app_step = decorators.App(
57
+ app_id="echo",
58
+ run_configuration=nextmv.RunConfiguration(
59
+ execution_class="4c8gb12h",
60
+ secrets_collection_id="some-secrets",
61
+ ),
62
+ )
63
+ app_run_config = AppRunConfig(
64
+ input={"data": [1, 2, 3]},
65
+ run_configuration=nextmv.RunConfiguration(execution_class="8c16gb12h"),
66
+ )
67
+ run_kwargs, _ = _prepare([app_run_config], app_step)
68
+ self.assertEqual(run_kwargs["configuration"].execution_class, "8c16gb12h")
69
+ self.assertIsNone(run_kwargs["configuration"].secrets_collection_id)
70
+
71
+ def test_app_run_config_without_run_configuration_keeps_decorator(self):
72
+ app_step = decorators.App(
73
+ app_id="echo",
74
+ run_configuration=nextmv.RunConfiguration(execution_class="4c8gb12h"),
75
+ )
76
+ app_run_config = AppRunConfig(input={"data": [1, 2, 3]})
77
+ run_kwargs, _ = _prepare([app_run_config], app_step)
78
+ self.assertEqual(run_kwargs["configuration"].execution_class, "4c8gb12h")
79
+
80
+ def test_no_instance_id(self):
81
+ app_step = decorators.App(app_id="echo")
82
+ _, instance_id = _prepare([{"data": [1, 2, 3]}], app_step)
83
+ self.assertEqual(instance_id, "")
84
+
85
+ def test_decorator_instance_id(self):
86
+ app_step = decorators.App(app_id="echo", instance_id="decorator-instance")
87
+ _, instance_id = _prepare([{"data": [1, 2, 3]}], app_step)
88
+ self.assertEqual(instance_id, "decorator-instance")
89
+
90
+ def test_app_run_config_instance_id_replaces_decorator(self):
91
+ app_step = decorators.App(app_id="echo", instance_id="decorator-instance")
92
+ app_run_config = AppRunConfig(input={"data": [1, 2, 3]}, instance_id="per-run-instance")
93
+ _, instance_id = _prepare([app_run_config], app_step)
94
+ self.assertEqual(instance_id, "per-run-instance")
95
+
96
+ def test_app_run_config_without_instance_id_keeps_decorator(self):
97
+ app_step = decorators.App(app_id="echo", instance_id="decorator-instance")
98
+ app_run_config = AppRunConfig(input={"data": [1, 2, 3]})
99
+ _, instance_id = _prepare([app_run_config], app_step)
100
+ self.assertEqual(instance_id, "decorator-instance")
101
+
102
+
103
+ if __name__ == "__main__":
104
+ unittest.main()
@@ -0,0 +1,69 @@
1
+ import os
2
+ import sys
3
+ import unittest
4
+
5
+ import nextmv
6
+
7
+ from nextpipe import AppOption, AppRunConfig
8
+
9
+ # Add the parent directory to the sys.path to allow imports from the main package. This
10
+ # is meant to help VS Code testing features.
11
+ sys.path.append(os.path.dirname(sys.path[0]))
12
+
13
+
14
+ class TestAppRunConfig(unittest.TestCase):
15
+ def test_options_dict(self):
16
+ config = AppRunConfig(input={"data": [1, 2, 3]}, options={"threads": 4, "verbose": True}, name="test-run")
17
+ options = config.get_options()
18
+ self.assertEqual(options["threads"], "4")
19
+ self.assertEqual(options["verbose"], "True")
20
+
21
+ def test_options_obj(self):
22
+ config = AppRunConfig(
23
+ input={"data": [1, 2, 3]},
24
+ options=[AppOption(name="threads", value=4), AppOption(name="verbose", value=True)],
25
+ name="test-run",
26
+ )
27
+ options = config.get_options()
28
+ self.assertEqual(options["threads"], "4")
29
+ self.assertTrue(options["verbose"], "True")
30
+
31
+ def test_run_configuration_default(self):
32
+ config = AppRunConfig(input={"data": [1, 2, 3]})
33
+ self.assertIsNone(config.run_configuration)
34
+
35
+ def test_run_configuration(self):
36
+ config = AppRunConfig(
37
+ input={"data": [1, 2, 3]},
38
+ run_configuration=nextmv.RunConfiguration(execution_class="8c16gb12h"),
39
+ )
40
+ self.assertEqual(config.run_configuration.execution_class, "8c16gb12h")
41
+
42
+ def test_run_configuration_json_round_trip(self):
43
+ config = AppRunConfig(
44
+ input={"data": [1, 2, 3]},
45
+ options={"threads": 4},
46
+ run_configuration=nextmv.RunConfiguration(
47
+ execution_class="8c16gb12h",
48
+ secrets_collection_id="some-secrets",
49
+ ),
50
+ )
51
+ restored = AppRunConfig.from_json(config.to_json())
52
+ self.assertIsInstance(restored.run_configuration, nextmv.RunConfiguration)
53
+ self.assertEqual(restored.run_configuration.execution_class, "8c16gb12h")
54
+ self.assertEqual(restored.run_configuration.secrets_collection_id, "some-secrets")
55
+
56
+ def test_run_configuration_json_round_trip_none(self):
57
+ config = AppRunConfig(input={"data": [1, 2, 3]})
58
+ restored = AppRunConfig.from_json(config.to_json())
59
+ self.assertIsNone(restored.run_configuration)
60
+
61
+ def test_instance_id_default(self):
62
+ config = AppRunConfig(input={"data": [1, 2, 3]})
63
+ self.assertIsNone(config.instance_id)
64
+
65
+ def test_instance_id(self):
66
+ config = AppRunConfig(input={"data": [1, 2, 3]}, instance_id="tuned-for-large")
67
+ self.assertEqual(config.instance_id, "tuned-for-large")
68
+ restored = AppRunConfig.from_json(config.to_json())
69
+ self.assertEqual(restored.instance_id, "tuned-for-large")
@@ -1 +0,0 @@
1
- __version__ = "v0.7.2"
@@ -1,27 +0,0 @@
1
- import os
2
- import sys
3
- import unittest
4
-
5
- from nextpipe import AppOption, AppRunConfig
6
-
7
- # Add the parent directory to the sys.path to allow imports from the main package. This
8
- # is meant to help VS Code testing features.
9
- sys.path.append(os.path.dirname(sys.path[0]))
10
-
11
-
12
- class TestAppRunConfig(unittest.TestCase):
13
- def test_options_dict(self):
14
- config = AppRunConfig(input={"data": [1, 2, 3]}, options={"threads": 4, "verbose": True}, name="test-run")
15
- options = config.get_options()
16
- self.assertEqual(options["threads"], "4")
17
- self.assertEqual(options["verbose"], "True")
18
-
19
- def test_options_obj(self):
20
- config = AppRunConfig(
21
- input={"data": [1, 2, 3]},
22
- options=[AppOption(name="threads", value=4), AppOption(name="verbose", value=True)],
23
- name="test-run",
24
- )
25
- options = config.get_options()
26
- self.assertEqual(options["threads"], "4")
27
- self.assertTrue(options["verbose"], "True")
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