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