runnable 0.1.0__py3-none-any.whl → 0.2.0__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.
Files changed (71) hide show
  1. runnable/__init__.py +34 -0
  2. runnable/catalog.py +141 -0
  3. runnable/cli.py +272 -0
  4. runnable/context.py +34 -0
  5. runnable/datastore.py +686 -0
  6. runnable/defaults.py +179 -0
  7. runnable/entrypoints.py +484 -0
  8. runnable/exceptions.py +94 -0
  9. runnable/executor.py +431 -0
  10. runnable/experiment_tracker.py +139 -0
  11. runnable/extensions/catalog/__init__.py +21 -0
  12. runnable/extensions/catalog/file_system/__init__.py +0 -0
  13. runnable/extensions/catalog/file_system/implementation.py +226 -0
  14. runnable/extensions/catalog/k8s_pvc/__init__.py +0 -0
  15. runnable/extensions/catalog/k8s_pvc/implementation.py +16 -0
  16. runnable/extensions/catalog/k8s_pvc/integration.py +59 -0
  17. runnable/extensions/executor/__init__.py +714 -0
  18. runnable/extensions/executor/argo/__init__.py +0 -0
  19. runnable/extensions/executor/argo/implementation.py +1182 -0
  20. runnable/extensions/executor/argo/specification.yaml +51 -0
  21. runnable/extensions/executor/k8s_job/__init__.py +0 -0
  22. runnable/extensions/executor/k8s_job/implementation_FF.py +259 -0
  23. runnable/extensions/executor/k8s_job/integration_FF.py +69 -0
  24. runnable/extensions/executor/local/__init__.py +0 -0
  25. runnable/extensions/executor/local/implementation.py +69 -0
  26. runnable/extensions/executor/local_container/__init__.py +0 -0
  27. runnable/extensions/executor/local_container/implementation.py +367 -0
  28. runnable/extensions/executor/mocked/__init__.py +0 -0
  29. runnable/extensions/executor/mocked/implementation.py +220 -0
  30. runnable/extensions/experiment_tracker/__init__.py +0 -0
  31. runnable/extensions/experiment_tracker/mlflow/__init__.py +0 -0
  32. runnable/extensions/experiment_tracker/mlflow/implementation.py +94 -0
  33. runnable/extensions/nodes.py +675 -0
  34. runnable/extensions/run_log_store/__init__.py +0 -0
  35. runnable/extensions/run_log_store/chunked_file_system/__init__.py +0 -0
  36. runnable/extensions/run_log_store/chunked_file_system/implementation.py +106 -0
  37. runnable/extensions/run_log_store/chunked_k8s_pvc/__init__.py +0 -0
  38. runnable/extensions/run_log_store/chunked_k8s_pvc/implementation.py +21 -0
  39. runnable/extensions/run_log_store/chunked_k8s_pvc/integration.py +61 -0
  40. runnable/extensions/run_log_store/db/implementation_FF.py +157 -0
  41. runnable/extensions/run_log_store/db/integration_FF.py +0 -0
  42. runnable/extensions/run_log_store/file_system/__init__.py +0 -0
  43. runnable/extensions/run_log_store/file_system/implementation.py +136 -0
  44. runnable/extensions/run_log_store/generic_chunked.py +541 -0
  45. runnable/extensions/run_log_store/k8s_pvc/__init__.py +0 -0
  46. runnable/extensions/run_log_store/k8s_pvc/implementation.py +21 -0
  47. runnable/extensions/run_log_store/k8s_pvc/integration.py +56 -0
  48. runnable/extensions/secrets/__init__.py +0 -0
  49. runnable/extensions/secrets/dotenv/__init__.py +0 -0
  50. runnable/extensions/secrets/dotenv/implementation.py +100 -0
  51. runnable/extensions/secrets/env_secrets/__init__.py +0 -0
  52. runnable/extensions/secrets/env_secrets/implementation.py +42 -0
  53. runnable/graph.py +464 -0
  54. runnable/integration.py +205 -0
  55. runnable/interaction.py +399 -0
  56. runnable/names.py +546 -0
  57. runnable/nodes.py +489 -0
  58. runnable/parameters.py +183 -0
  59. runnable/pickler.py +102 -0
  60. runnable/sdk.py +470 -0
  61. runnable/secrets.py +95 -0
  62. runnable/tasks.py +392 -0
  63. runnable/utils.py +630 -0
  64. runnable-0.2.0.dist-info/METADATA +437 -0
  65. runnable-0.2.0.dist-info/RECORD +69 -0
  66. runnable-0.2.0.dist-info/entry_points.txt +44 -0
  67. runnable-0.1.0.dist-info/METADATA +0 -16
  68. runnable-0.1.0.dist-info/RECORD +0 -6
  69. /runnable/{.gitkeep → extensions/__init__.py} +0 -0
  70. {runnable-0.1.0.dist-info → runnable-0.2.0.dist-info}/LICENSE +0 -0
  71. {runnable-0.1.0.dist-info → runnable-0.2.0.dist-info}/WHEEL +0 -0
runnable/defaults.py ADDED
@@ -0,0 +1,179 @@
1
+ # mypy: ignore-errors
2
+ # The above should be done until https://github.com/python/mypy/issues/8823
3
+ from enum import Enum
4
+ from typing import Any, Dict, Mapping, Optional, Union
5
+
6
+ from typing_extensions import TypeAlias
7
+
8
+ # TODO: This is not the correct way to do this.
9
+ try: # pragma: no cover
10
+ from typing import TypedDict # type: ignore[unused-ignore]
11
+ except ImportError: # pragma: no cover
12
+ from typing_extensions import TypedDict # type: ignore[unused-ignore]
13
+
14
+
15
+ NAME = "magnus"
16
+ LOGGER_NAME = "magnus"
17
+
18
+ # CLI settings
19
+ LOG_LEVEL = "WARNING"
20
+
21
+
22
+ class EXECUTION_PLAN(Enum):
23
+ """
24
+ The possible execution plans for a magnus job.
25
+ """
26
+
27
+ CHAINED = "chained" #  121 relationship between run log and the dag.
28
+ UNCHAINED = "unchained" # Only captures execution of steps, no relation.
29
+ INTERACTIVE = "interactive" # used for interactive sessions
30
+
31
+
32
+ # Type definitions
33
+ class ServiceConfig(TypedDict):
34
+ type: str
35
+ config: Mapping[str, Any]
36
+
37
+
38
+ class MagnusConfig(TypedDict, total=False):
39
+ run_log_store: Optional[ServiceConfig]
40
+ secrets: Optional[ServiceConfig]
41
+ catalog: Optional[ServiceConfig]
42
+ executor: Optional[ServiceConfig]
43
+ experiment_tracker: Optional[ServiceConfig]
44
+
45
+
46
+ TypeMapVariable: TypeAlias = Optional[Dict[str, Union[str, int, float]]]
47
+
48
+
49
+ # Config file environment variable
50
+ MAGNUS_CONFIG_FILE = "MAGNUS_CONFIG_FILE"
51
+ MAGNUS_RUN_TAG = "MAGNUS_RUN_TAG"
52
+
53
+ # Interaction settings
54
+ TRACK_PREFIX = "MAGNUS_TRACK_"
55
+ STEP_INDICATOR = "_STEP_"
56
+ PARAMETER_PREFIX = "MAGNUS_PRM_"
57
+ MAP_VARIABLE = "MAGNUS_MAP_VARIABLE"
58
+ VARIABLE_PREFIX = "MAGNUS_VAR_"
59
+ ENV_RUN_ID = "MAGNUS_RUN_ID"
60
+ ATTEMPT_NUMBER = "MAGNUS_STEP_ATTEMPT"
61
+
62
+ # STATUS progression
63
+ # For Branch, CREATED -> PROCESSING -> SUCCESS OR FAIL
64
+ # For a step, CREATED -> TRIGGERED -> PROCESSING -> SUCCESS OR FAIL
65
+ CREATED = "CREATED"
66
+ PROCESSING = "PROCESSING"
67
+ SUCCESS = "SUCCESS"
68
+ FAIL = "FAIL"
69
+ TRIGGERED = "TRIGGERED"
70
+
71
+ # Node and Command settings
72
+ COMMAND_TYPE = "python"
73
+ NODE_SPEC_FILE = "node_spec.yaml"
74
+ COMMAND_FRIENDLY_CHARACTER = "%"
75
+ DEFAULT_CONTAINER_CONTEXT_PATH = "/opt/magnus/"
76
+ DEFAULT_CONTAINER_DATA_PATH = "data/"
77
+ DEFAULT_CONTAINER_OUTPUT_PARAMETERS = "parameters.json"
78
+
79
+ # Default services
80
+ DEFAULT_EXECUTOR = ServiceConfig(type="local", config={})
81
+ DEFAULT_RUN_LOG_STORE = ServiceConfig(type="buffered", config={})
82
+ DEFAULT_CATALOG = ServiceConfig(type="file-system", config={})
83
+ DEFAULT_SECRETS = ServiceConfig(type="do-nothing", config={})
84
+ DEFAULT_EXPERIMENT_TRACKER = ServiceConfig(type="do-nothing", config={})
85
+
86
+ # Map state
87
+ MAP_PLACEHOLDER = "map_variable_placeholder"
88
+
89
+ # Dag node
90
+ DAG_BRANCH_NAME = "dag"
91
+
92
+ # RUN settings
93
+ RANDOM_RUN_ID_LEN = 6
94
+ MAX_TIME = 86400 # 1 day in seconds
95
+
96
+ # User extensions
97
+ USER_CONFIG_FILE = "magnus-config.yaml"
98
+
99
+ # Executor settings
100
+ ENABLE_PARALLEL = False
101
+
102
+ # RUN log store settings
103
+ LOG_LOCATION_FOLDER = ".run_log_store"
104
+
105
+ # Dag node
106
+ DAG_BRANCH_NAME = "dag"
107
+
108
+ # Data catalog settings
109
+ CATALOG_LOCATION_FOLDER = ".catalog"
110
+ COMPUTE_DATA_FOLDER = "."
111
+
112
+ # Secrets settings
113
+ DOTENV_FILE_LOCATION = ".env"
114
+
115
+
116
+ # Docker settings
117
+ DOCKERFILE_NAME = "Dockerfile"
118
+ DOCKERFILE_CONTENT = r"""# Python 3.8 Image without Dependecies
119
+ FROM python:3.8
120
+
121
+ LABEL maintainer="mesanthu@gmail.com"
122
+
123
+ RUN apt-get update && apt-get install -y --no-install-recommends \
124
+ git \
125
+ && rm -rf /var/lib/apt/lists/*
126
+
127
+ ${INSTALL_STYLE}
128
+
129
+ ENV VIRTUAL_ENV=/opt/venv
130
+ RUN python -m virtualenv --python=/usr/local/bin/python $VIRTUAL_ENV
131
+ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
132
+
133
+ ${COPY_CONTENT}
134
+ WORKDIR /app
135
+
136
+ ${INSTALL_REQUIREMENTS}
137
+ """
138
+ GIT_ARCHIVE_NAME = "git_tracked"
139
+ LEN_SHA_FOR_TAG = 8
140
+
141
+
142
+ class ENTRYPOINT(Enum):
143
+ """
144
+ The possible container entrypoint types.
145
+ """
146
+
147
+ USER = "user"
148
+ SYSTEM = "system"
149
+
150
+
151
+ ## Logging settings
152
+
153
+ LOGGING_CONFIG = {
154
+ "version": 1,
155
+ "disable_existing_loggers": True,
156
+ "formatters": {
157
+ "standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"},
158
+ "magnus_formatter": {"format": "%(message)s", "datefmt": "[%X]"},
159
+ },
160
+ "handlers": {
161
+ "default": {
162
+ "formatter": "standard",
163
+ "class": "logging.StreamHandler",
164
+ "stream": "ext://sys.stdout", # Default is stderr
165
+ },
166
+ "magnus_handler": {
167
+ "formatter": "magnus_formatter",
168
+ "class": "rich.logging.RichHandler",
169
+ "rich_tracebacks": True,
170
+ },
171
+ },
172
+ "loggers": {
173
+ "": {
174
+ "handlers": ["default"],
175
+ "propagate": True,
176
+ }, # Root logger
177
+ LOGGER_NAME: {"handlers": ["magnus_handler"], "propagate": False},
178
+ },
179
+ }
@@ -0,0 +1,484 @@
1
+ import json
2
+ import logging
3
+ from typing import Optional, cast
4
+
5
+ from rich import print
6
+
7
+ import runnable.context as context
8
+ from runnable import defaults, graph, utils
9
+ from runnable.defaults import MagnusConfig, ServiceConfig
10
+
11
+ logger = logging.getLogger(defaults.LOGGER_NAME)
12
+
13
+
14
+ def get_default_configs() -> MagnusConfig:
15
+ """
16
+ User can provide extensions as part of their code base, magnus-config.yaml provides the place to put them.
17
+ """
18
+ user_configs = {}
19
+ if utils.does_file_exist(defaults.USER_CONFIG_FILE):
20
+ user_configs = utils.load_yaml(defaults.USER_CONFIG_FILE)
21
+
22
+ if not user_configs:
23
+ return {}
24
+
25
+ user_defaults = user_configs.get("defaults", {})
26
+ if user_defaults:
27
+ return user_defaults
28
+
29
+ return {}
30
+
31
+
32
+ def prepare_configurations(
33
+ run_id: str,
34
+ configuration_file: str = "",
35
+ pipeline_file: str = "",
36
+ tag: str = "",
37
+ use_cached: str = "",
38
+ parameters_file: str = "",
39
+ force_local_executor: bool = False,
40
+ ) -> context.Context:
41
+ """
42
+ Replace the placeholders in the dag/config against the variables file.
43
+
44
+ Attach the secrets_handler, run_log_store, catalog_handler to the executor and return it.
45
+
46
+ Args:
47
+ variables_file (str): The variables file, if used or None
48
+ pipeline_file (str): The config/dag file
49
+ run_id (str): The run id of the run.
50
+ tag (str): If a tag is provided at the run time
51
+ use_cached (str): Provide the run_id of the older run
52
+
53
+ Returns:
54
+ executor.BaseExecutor : A prepared executor as per the dag/config
55
+ """
56
+ magnus_defaults = get_default_configs()
57
+
58
+ variables = utils.gather_variables()
59
+
60
+ templated_configuration = {}
61
+ if configuration_file:
62
+ templated_configuration = utils.load_yaml(configuration_file) or {}
63
+
64
+ configuration: MagnusConfig = cast(MagnusConfig, templated_configuration)
65
+
66
+ # Run log settings, configuration over-rides everything
67
+ run_log_config: Optional[ServiceConfig] = configuration.get("run_log_store", None)
68
+ if not run_log_config:
69
+ run_log_config = cast(ServiceConfig, magnus_defaults.get("run_log_store", defaults.DEFAULT_RUN_LOG_STORE))
70
+ run_log_store = utils.get_provider_by_name_and_type("run_log_store", run_log_config)
71
+
72
+ # Catalog handler settings, configuration over-rides everything
73
+ catalog_config: Optional[ServiceConfig] = configuration.get("catalog", None)
74
+ if not catalog_config:
75
+ catalog_config = cast(ServiceConfig, magnus_defaults.get("catalog", defaults.DEFAULT_CATALOG))
76
+ catalog_handler = utils.get_provider_by_name_and_type("catalog", catalog_config)
77
+
78
+ # Secret handler settings, configuration over-rides everything
79
+ secrets_config: Optional[ServiceConfig] = configuration.get("secrets", None)
80
+ if not secrets_config:
81
+ secrets_config = cast(ServiceConfig, magnus_defaults.get("secrets", defaults.DEFAULT_SECRETS))
82
+ secrets_handler = utils.get_provider_by_name_and_type("secrets", secrets_config)
83
+
84
+ # experiment tracker settings, configuration over-rides everything
85
+ tracker_config: Optional[ServiceConfig] = configuration.get("experiment_tracker", None)
86
+ if not tracker_config:
87
+ tracker_config = cast(
88
+ ServiceConfig, magnus_defaults.get("experiment_tracker", defaults.DEFAULT_EXPERIMENT_TRACKER)
89
+ )
90
+ tracker_handler = utils.get_provider_by_name_and_type("experiment_tracker", tracker_config)
91
+
92
+ # executor configurations, configuration over rides everything
93
+ executor_config: Optional[ServiceConfig] = configuration.get("executor", None)
94
+ if force_local_executor:
95
+ executor_config = ServiceConfig(type="local", config={})
96
+
97
+ if not executor_config:
98
+ executor_config = cast(ServiceConfig, magnus_defaults.get("executor", defaults.DEFAULT_EXECUTOR))
99
+ configured_executor = utils.get_provider_by_name_and_type("executor", executor_config)
100
+
101
+ # Construct the context
102
+ run_context = context.Context(
103
+ executor=configured_executor,
104
+ run_log_store=run_log_store,
105
+ catalog_handler=catalog_handler,
106
+ secrets_handler=secrets_handler,
107
+ experiment_tracker=tracker_handler,
108
+ variables=variables,
109
+ tag=tag,
110
+ run_id=run_id,
111
+ configuration_file=configuration_file,
112
+ parameters_file=parameters_file,
113
+ )
114
+
115
+ if pipeline_file:
116
+ # There are use cases where we are only preparing the executor
117
+ pipeline_config = utils.load_yaml(pipeline_file)
118
+
119
+ logger.info("The input pipeline:")
120
+ logger.info(json.dumps(pipeline_config, indent=4))
121
+
122
+ # Create the graph
123
+ dag_config = pipeline_config["dag"]
124
+ dag_hash = utils.get_dag_hash(dag_config)
125
+ dag = graph.create_graph(dag_config)
126
+
127
+ run_context.pipeline_file = pipeline_file
128
+ run_context.dag = dag
129
+ run_context.dag_hash = dag_hash
130
+
131
+ run_context.use_cached = False
132
+ if use_cached:
133
+ run_context.use_cached = True
134
+ run_context.original_run_id = use_cached
135
+
136
+ context.run_context = run_context
137
+
138
+ return run_context
139
+
140
+
141
+ def execute(
142
+ configuration_file: str,
143
+ pipeline_file: str,
144
+ tag: str = "",
145
+ run_id: str = "",
146
+ use_cached: str = "",
147
+ parameters_file: str = "",
148
+ ):
149
+ # pylint: disable=R0914,R0913
150
+ """
151
+ The entry point to magnus execution. This method would prepare the configurations and delegates traversal to the
152
+ executor
153
+
154
+ Args:
155
+ pipeline_file (str): The config/dag file
156
+ run_id (str): The run id of the run.
157
+ tag (str): If a tag is provided at the run time
158
+ use_cached (str): The previous run_id to use.
159
+ parameters_file (str): The parameters being sent in to the application
160
+ """
161
+ # Re run settings
162
+ run_id = utils.generate_run_id(run_id=run_id)
163
+
164
+ run_context = prepare_configurations(
165
+ configuration_file=configuration_file,
166
+ pipeline_file=pipeline_file,
167
+ run_id=run_id,
168
+ tag=tag,
169
+ use_cached=use_cached,
170
+ parameters_file=parameters_file,
171
+ )
172
+ print("Working with context:")
173
+ print(run_context)
174
+
175
+ executor = run_context.executor
176
+
177
+ run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
178
+
179
+ utils.set_magnus_environment_variables(run_id=run_id, configuration_file=configuration_file, tag=tag)
180
+
181
+ # Prepare for graph execution
182
+ executor.prepare_for_graph_execution()
183
+
184
+ logger.info("Executing the graph")
185
+ executor.execute_graph(dag=run_context.dag) # type: ignore
186
+
187
+ executor.send_return_code()
188
+
189
+
190
+ def execute_single_node(
191
+ configuration_file: str,
192
+ pipeline_file: str,
193
+ step_name: str,
194
+ map_variable: str,
195
+ run_id: str,
196
+ tag: str = "",
197
+ parameters_file: str = "",
198
+ ):
199
+ """
200
+ The entry point into executing a single node of magnus. Orchestration modes should extensively use this
201
+ entry point.
202
+
203
+ It should have similar set up of configurations to execute because orchestrator modes can initiate the execution.
204
+
205
+ Args:
206
+ variables_file (str): The variables file, if used or None
207
+ step_name : The name of the step to execute in dot path convention
208
+ pipeline_file (str): The config/dag file
209
+ run_id (str): The run id of the run.
210
+ tag (str): If a tag is provided at the run time
211
+ parameters_file (str): The parameters being sent in to the application
212
+
213
+ """
214
+ from runnable import nodes
215
+
216
+ run_context = prepare_configurations(
217
+ configuration_file=configuration_file,
218
+ pipeline_file=pipeline_file,
219
+ run_id=run_id,
220
+ tag=tag,
221
+ use_cached="",
222
+ parameters_file=parameters_file,
223
+ )
224
+ print("Working with context:")
225
+ print(run_context)
226
+
227
+ executor = run_context.executor
228
+ run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
229
+ utils.set_magnus_environment_variables(run_id=run_id, configuration_file=configuration_file, tag=tag)
230
+
231
+ executor.prepare_for_node_execution()
232
+
233
+ if not run_context.dag:
234
+ # There are a few entry points that make graph dynamically and do not have a dag defined statically.
235
+ run_log = run_context.run_log_store.get_run_log_by_id(run_id=run_id, full=False)
236
+ run_context.dag = graph.create_graph(run_log.run_config["pipeline"])
237
+
238
+ step_internal_name = nodes.BaseNode._get_internal_name_from_command_name(step_name)
239
+
240
+ map_variable_dict = utils.json_to_ordered_dict(map_variable)
241
+
242
+ node_to_execute, _ = graph.search_node_by_internal_name(run_context.dag, step_internal_name)
243
+
244
+ logger.info("Executing the single node of : %s", node_to_execute)
245
+ executor.execute_node(node=node_to_execute, map_variable=map_variable_dict)
246
+
247
+ executor.send_return_code(stage="execution")
248
+
249
+
250
+ def execute_single_brach(
251
+ configuration_file: str,
252
+ pipeline_file: str,
253
+ branch_name: str,
254
+ map_variable: str,
255
+ run_id: str,
256
+ tag: str,
257
+ ):
258
+ """
259
+ The entry point into executing a branch of the graph. Interactive modes in parallel runs use this to execute
260
+ branches in parallel.
261
+
262
+ This entry point is never used by its own but rather from a node. So the arguments sent into this are fewer.
263
+
264
+ Args:
265
+ variables_file (str): The variables file, if used or None
266
+ branch_name : The name of the branch to execute, in dot.path.convention
267
+ pipeline_file (str): The config/dag file
268
+ run_id (str): The run id of the run.
269
+ tag (str): If a tag is provided at the run time
270
+ """
271
+ from runnable import nodes
272
+
273
+ run_context = prepare_configurations(
274
+ configuration_file=configuration_file,
275
+ pipeline_file=pipeline_file,
276
+ run_id=run_id,
277
+ tag=tag,
278
+ use_cached="",
279
+ )
280
+ print("Working with context:")
281
+ print(run_context)
282
+
283
+ executor = run_context.executor
284
+ run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
285
+ utils.set_magnus_environment_variables(run_id=run_id, configuration_file=configuration_file, tag=tag)
286
+
287
+ branch_internal_name = nodes.BaseNode._get_internal_name_from_command_name(branch_name)
288
+
289
+ map_variable_dict = utils.json_to_ordered_dict(map_variable)
290
+
291
+ branch_to_execute = graph.search_branch_by_internal_name(run_context.dag, branch_internal_name) # type: ignore
292
+
293
+ logger.info("Executing the single branch of %s", branch_to_execute)
294
+ executor.execute_graph(dag=branch_to_execute, map_variable=map_variable_dict)
295
+
296
+ executor.send_return_code()
297
+
298
+
299
+ def execute_notebook(
300
+ entrypoint: str,
301
+ notebook_file: str,
302
+ catalog_config: dict,
303
+ configuration_file: str,
304
+ notebook_output_path: str = "",
305
+ tag: str = "",
306
+ run_id: str = "",
307
+ parameters_file: str = "",
308
+ ):
309
+ """
310
+ The entry point to magnus execution of a notebook. This method would prepare the configurations and
311
+ delegates traversal to the executor
312
+ """
313
+ run_id = utils.generate_run_id(run_id=run_id)
314
+
315
+ run_context = prepare_configurations(
316
+ configuration_file=configuration_file,
317
+ run_id=run_id,
318
+ tag=tag,
319
+ parameters_file=parameters_file,
320
+ )
321
+
322
+ executor = run_context.executor
323
+ run_context.execution_plan = defaults.EXECUTION_PLAN.UNCHAINED.value
324
+ utils.set_magnus_environment_variables(run_id=run_id, configuration_file=configuration_file, tag=tag)
325
+
326
+ print("Working with context:")
327
+ print(run_context)
328
+
329
+ step_config = {
330
+ "command": notebook_file,
331
+ "command_type": "notebook",
332
+ "notebook_output_path": notebook_output_path,
333
+ "type": "task",
334
+ "next": "success",
335
+ "catalog": catalog_config,
336
+ }
337
+ node = graph.create_node(name="executing job", step_config=step_config)
338
+
339
+ if entrypoint == defaults.ENTRYPOINT.USER.value:
340
+ # Prepare for graph execution
341
+ executor.prepare_for_graph_execution()
342
+
343
+ logger.info("Executing the job from the user. We are still in the caller's compute environment")
344
+ executor.execute_job(node=node)
345
+
346
+ elif entrypoint == defaults.ENTRYPOINT.SYSTEM.value:
347
+ executor.prepare_for_node_execution()
348
+ logger.info("Executing the job from the system. We are in the config's compute environment")
349
+ executor.execute_node(node=node)
350
+
351
+ # Update the status of the run log
352
+ step_log = run_context.run_log_store.get_step_log(node._get_step_log_name(), run_id)
353
+ run_context.run_log_store.update_run_log_status(run_id=run_id, status=step_log.status)
354
+
355
+ else:
356
+ raise ValueError(f"Invalid entrypoint {entrypoint}")
357
+
358
+ executor.send_return_code()
359
+
360
+
361
+ def execute_function(
362
+ entrypoint: str,
363
+ command: str,
364
+ catalog_config: dict,
365
+ configuration_file: str,
366
+ tag: str = "",
367
+ run_id: str = "",
368
+ parameters_file: str = "",
369
+ ):
370
+ """
371
+ The entry point to magnus execution of a function. This method would prepare the configurations and
372
+ delegates traversal to the executor
373
+ """
374
+ run_id = utils.generate_run_id(run_id=run_id)
375
+
376
+ run_context = prepare_configurations(
377
+ configuration_file=configuration_file,
378
+ run_id=run_id,
379
+ tag=tag,
380
+ parameters_file=parameters_file,
381
+ )
382
+
383
+ executor = run_context.executor
384
+
385
+ run_context.execution_plan = defaults.EXECUTION_PLAN.UNCHAINED.value
386
+ utils.set_magnus_environment_variables(run_id=run_id, configuration_file=configuration_file, tag=tag)
387
+
388
+ print("Working with context:")
389
+ print(run_context)
390
+
391
+ # Prepare the graph with a single node
392
+ step_config = {
393
+ "command": command,
394
+ "command_type": "python",
395
+ "type": "task",
396
+ "next": "success",
397
+ "catalog": catalog_config,
398
+ }
399
+ node = graph.create_node(name="executing job", step_config=step_config)
400
+
401
+ if entrypoint == defaults.ENTRYPOINT.USER.value:
402
+ # Prepare for graph execution
403
+ executor.prepare_for_graph_execution()
404
+
405
+ logger.info("Executing the job from the user. We are still in the caller's compute environment")
406
+ executor.execute_job(node=node)
407
+
408
+ elif entrypoint == defaults.ENTRYPOINT.SYSTEM.value:
409
+ executor.prepare_for_node_execution()
410
+ logger.info("Executing the job from the system. We are in the config's compute environment")
411
+ executor.execute_node(node=node)
412
+
413
+ # Update the status of the run log
414
+ step_log = run_context.run_log_store.get_step_log(node._get_step_log_name(), run_id)
415
+ run_context.run_log_store.update_run_log_status(run_id=run_id, status=step_log.status)
416
+
417
+ else:
418
+ raise ValueError(f"Invalid entrypoint {entrypoint}")
419
+
420
+ executor.send_return_code()
421
+
422
+
423
+ def fan(
424
+ configuration_file: str,
425
+ pipeline_file: str,
426
+ step_name: str,
427
+ mode: str,
428
+ map_variable: str,
429
+ run_id: str,
430
+ tag: str = "",
431
+ parameters_file: str = "",
432
+ ):
433
+ """
434
+ The entry point to either fan in or out for a composite node. Only 3rd party orchestrators should use this.
435
+
436
+ It should have similar set up of configurations to execute because orchestrator modes can initiate the execution.
437
+
438
+ Args:
439
+ configuration_file (str): The configuration file.
440
+ mode: in or out
441
+ step_name : The name of the step to execute in dot path convention
442
+ pipeline_file (str): The config/dag file
443
+ run_id (str): The run id of the run.
444
+ tag (str): If a tag is provided at the run time
445
+ parameters_file (str): The parameters being sent in to the application
446
+
447
+ """
448
+ from runnable import nodes
449
+
450
+ run_context = prepare_configurations(
451
+ configuration_file=configuration_file,
452
+ pipeline_file=pipeline_file,
453
+ run_id=run_id,
454
+ tag=tag,
455
+ use_cached="",
456
+ parameters_file=parameters_file,
457
+ )
458
+ print("Working with context:")
459
+ print(run_context)
460
+
461
+ executor = run_context.executor
462
+ run_context.execution_plan = defaults.EXECUTION_PLAN.CHAINED.value
463
+ utils.set_magnus_environment_variables(run_id=run_id, configuration_file=configuration_file, tag=tag)
464
+
465
+ executor.prepare_for_node_execution()
466
+
467
+ step_internal_name = nodes.BaseNode._get_internal_name_from_command_name(step_name)
468
+ node_to_execute, _ = graph.search_node_by_internal_name(run_context.dag, step_internal_name) # type: ignore
469
+
470
+ map_variable_dict = utils.json_to_ordered_dict(map_variable)
471
+
472
+ if mode == "in":
473
+ logger.info("Fanning in for : %s", node_to_execute)
474
+ executor.fan_in(node=node_to_execute, map_variable=map_variable_dict)
475
+ elif mode == "out":
476
+ logger.info("Fanning out for : %s", node_to_execute)
477
+ executor.fan_out(node=node_to_execute, map_variable=map_variable_dict)
478
+ else:
479
+ raise ValueError(f"Invalid mode {mode}")
480
+
481
+
482
+ if __name__ == "__main__":
483
+ # This is only for perf testing purposes.
484
+ prepare_configurations(run_id="abc", pipeline_file="example/mocking.yaml")