ob-metaflow 2.12.36.1__py2.py3-none-any.whl → 2.12.36.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ob-metaflow might be problematic. Click here for more details.
- metaflow/__init__.py +3 -0
- metaflow/cli.py +84 -697
- metaflow/cli_args.py +17 -0
- metaflow/cli_components/__init__.py +0 -0
- metaflow/cli_components/dump_cmd.py +96 -0
- metaflow/cli_components/init_cmd.py +51 -0
- metaflow/cli_components/run_cmds.py +358 -0
- metaflow/cli_components/step_cmd.py +189 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/cmd/develop/stub_generator.py +9 -2
- metaflow/decorators.py +63 -2
- metaflow/extension_support/plugins.py +41 -27
- metaflow/flowspec.py +156 -16
- metaflow/includefile.py +50 -22
- metaflow/metaflow_config.py +1 -1
- metaflow/package.py +17 -3
- metaflow/parameters.py +80 -23
- metaflow/plugins/__init__.py +4 -0
- metaflow/plugins/airflow/airflow_cli.py +1 -0
- metaflow/plugins/argo/argo_workflows.py +44 -4
- metaflow/plugins/argo/argo_workflows_cli.py +1 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +5 -1
- metaflow/plugins/aws/batch/batch_decorator.py +2 -2
- metaflow/plugins/aws/step_functions/step_functions.py +32 -0
- metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +3 -0
- metaflow/plugins/datatools/s3/s3op.py +3 -3
- metaflow/plugins/kubernetes/kubernetes.py +3 -3
- metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
- metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -2
- metaflow/plugins/kubernetes/kubernetes_job.py +3 -3
- metaflow/plugins/pypi/conda_decorator.py +20 -10
- metaflow/plugins/pypi/pypi_decorator.py +11 -9
- metaflow/plugins/timeout_decorator.py +2 -2
- metaflow/runner/click_api.py +73 -19
- metaflow/runner/deployer.py +1 -1
- metaflow/runner/deployer_impl.py +2 -2
- metaflow/runner/metaflow_runner.py +4 -1
- metaflow/runner/nbdeploy.py +2 -0
- metaflow/runner/nbrun.py +1 -1
- metaflow/runner/subprocess_manager.py +3 -1
- metaflow/runner/utils.py +37 -20
- metaflow/runtime.py +111 -73
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_decorators.py +563 -0
- metaflow/user_configs/config_options.py +495 -0
- metaflow/user_configs/config_parameters.py +386 -0
- metaflow/util.py +17 -0
- metaflow/version.py +1 -1
- {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/METADATA +3 -2
- {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/RECORD +56 -46
- {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/WHEEL +0 -0
- {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/top_level.txt +0 -0
|
@@ -61,6 +61,7 @@ from metaflow.plugins.kubernetes.kubernetes import (
|
|
|
61
61
|
)
|
|
62
62
|
from metaflow.plugins.kubernetes.kubernetes_jobsets import KubernetesArgoJobSet
|
|
63
63
|
from metaflow.unbounded_foreach import UBF_CONTROL, UBF_TASK
|
|
64
|
+
from metaflow.user_configs.config_options import ConfigInput
|
|
64
65
|
from metaflow.util import (
|
|
65
66
|
compress_list,
|
|
66
67
|
dict_to_cli_options,
|
|
@@ -169,6 +170,7 @@ class ArgoWorkflows(object):
|
|
|
169
170
|
self.enable_heartbeat_daemon = enable_heartbeat_daemon
|
|
170
171
|
self.enable_error_msg_capture = enable_error_msg_capture
|
|
171
172
|
self.parameters = self._process_parameters()
|
|
173
|
+
self.config_parameters = self._process_config_parameters()
|
|
172
174
|
self.triggers, self.trigger_options = self._process_triggers()
|
|
173
175
|
self._schedule, self._timezone = self._get_schedule()
|
|
174
176
|
|
|
@@ -456,6 +458,10 @@ class ArgoWorkflows(object):
|
|
|
456
458
|
"case-insensitive." % param.name
|
|
457
459
|
)
|
|
458
460
|
seen.add(norm)
|
|
461
|
+
# NOTE: We skip config parameters as these do not have dynamic values,
|
|
462
|
+
# and need to be treated differently.
|
|
463
|
+
if param.IS_CONFIG_PARAMETER:
|
|
464
|
+
continue
|
|
459
465
|
|
|
460
466
|
extra_attrs = {}
|
|
461
467
|
if param.kwargs.get("type") == JSONType:
|
|
@@ -489,6 +495,7 @@ class ArgoWorkflows(object):
|
|
|
489
495
|
# execution - which needs to be fixed imminently.
|
|
490
496
|
if not is_required or default_value is not None:
|
|
491
497
|
default_value = json.dumps(default_value)
|
|
498
|
+
|
|
492
499
|
parameters[param.name] = dict(
|
|
493
500
|
name=param.name,
|
|
494
501
|
value=default_value,
|
|
@@ -499,6 +506,27 @@ class ArgoWorkflows(object):
|
|
|
499
506
|
)
|
|
500
507
|
return parameters
|
|
501
508
|
|
|
509
|
+
def _process_config_parameters(self):
|
|
510
|
+
parameters = []
|
|
511
|
+
seen = set()
|
|
512
|
+
for var, param in self.flow._get_parameters():
|
|
513
|
+
if not param.IS_CONFIG_PARAMETER:
|
|
514
|
+
continue
|
|
515
|
+
# Throw an exception if the parameter is specified twice.
|
|
516
|
+
norm = param.name.lower()
|
|
517
|
+
if norm in seen:
|
|
518
|
+
raise MetaflowException(
|
|
519
|
+
"Parameter *%s* is specified twice. "
|
|
520
|
+
"Note that parameter names are "
|
|
521
|
+
"case-insensitive." % param.name
|
|
522
|
+
)
|
|
523
|
+
seen.add(norm)
|
|
524
|
+
|
|
525
|
+
parameters.append(
|
|
526
|
+
dict(name=param.name, kv_name=ConfigInput.make_key_name(param.name))
|
|
527
|
+
)
|
|
528
|
+
return parameters
|
|
529
|
+
|
|
502
530
|
def _process_triggers(self):
|
|
503
531
|
# Impute triggers for Argo Workflow Template specified through @trigger and
|
|
504
532
|
# @trigger_on_finish decorators
|
|
@@ -521,8 +549,13 @@ class ArgoWorkflows(object):
|
|
|
521
549
|
# convert them to lower case since Metaflow parameters are case
|
|
522
550
|
# insensitive.
|
|
523
551
|
seen = set()
|
|
552
|
+
# NOTE: We skip config parameters as their values can not be set through event payloads
|
|
524
553
|
params = set(
|
|
525
|
-
[
|
|
554
|
+
[
|
|
555
|
+
param.name.lower()
|
|
556
|
+
for var, param in self.flow._get_parameters()
|
|
557
|
+
if not param.IS_CONFIG_PARAMETER
|
|
558
|
+
]
|
|
526
559
|
)
|
|
527
560
|
trigger_deco = self.flow._flow_decorators.get("trigger")[0]
|
|
528
561
|
trigger_deco.format_deploytime_value()
|
|
@@ -1721,6 +1754,13 @@ class ArgoWorkflows(object):
|
|
|
1721
1754
|
metaflow_version["production_token"] = self.production_token
|
|
1722
1755
|
env["METAFLOW_VERSION"] = json.dumps(metaflow_version)
|
|
1723
1756
|
|
|
1757
|
+
# map config values
|
|
1758
|
+
cfg_env = {
|
|
1759
|
+
param["name"]: param["kv_name"] for param in self.config_parameters
|
|
1760
|
+
}
|
|
1761
|
+
if cfg_env:
|
|
1762
|
+
env["METAFLOW_FLOW_CONFIG_VALUE"] = json.dumps(cfg_env)
|
|
1763
|
+
|
|
1724
1764
|
# Set the template inputs and outputs for passing state. Very simply,
|
|
1725
1765
|
# the container template takes in input-paths as input and outputs
|
|
1726
1766
|
# the task-id (which feeds in as input-paths to the subsequent task).
|
|
@@ -1829,7 +1869,7 @@ class ArgoWorkflows(object):
|
|
|
1829
1869
|
|
|
1830
1870
|
# get initial configs
|
|
1831
1871
|
initial_configs = init_config()
|
|
1832
|
-
for entry in ["OBP_PERIMETER", "
|
|
1872
|
+
for entry in ["OBP_PERIMETER", "OBP_INTEGRATIONS_URL"]:
|
|
1833
1873
|
if entry not in initial_configs:
|
|
1834
1874
|
raise ArgoWorkflowsException(
|
|
1835
1875
|
f"{entry} was not found in metaflow config. Please make sure to run `outerbounds configure <...>` command which can be found on the Ourebounds UI or reach out to your Outerbounds support team."
|
|
@@ -1837,8 +1877,8 @@ class ArgoWorkflows(object):
|
|
|
1837
1877
|
|
|
1838
1878
|
additional_obp_configs = {
|
|
1839
1879
|
"OBP_PERIMETER": initial_configs["OBP_PERIMETER"],
|
|
1840
|
-
"
|
|
1841
|
-
"
|
|
1880
|
+
"OBP_INTEGRATIONS_URL": initial_configs[
|
|
1881
|
+
"OBP_INTEGRATIONS_URL"
|
|
1842
1882
|
],
|
|
1843
1883
|
}
|
|
1844
1884
|
|
|
@@ -470,6 +470,7 @@ def make_flow(
|
|
|
470
470
|
decorators._attach_decorators(
|
|
471
471
|
obj.flow, [KubernetesDecorator.name, EnvironmentDecorator.name]
|
|
472
472
|
)
|
|
473
|
+
decorators._init(obj.flow)
|
|
473
474
|
|
|
474
475
|
decorators._init_step_decorators(
|
|
475
476
|
obj.flow, obj.graph, obj.environment, obj.flow_datastore, obj.logger
|
|
@@ -97,6 +97,7 @@ class ArgoWorkflowsTriggeredRun(TriggeredRun):
|
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
command_obj = self.deployer.spm.get(pid)
|
|
100
|
+
command_obj.sync_wait()
|
|
100
101
|
return command_obj.process.returncode == 0
|
|
101
102
|
|
|
102
103
|
def unsuspend(self, **kwargs) -> bool:
|
|
@@ -131,6 +132,7 @@ class ArgoWorkflowsTriggeredRun(TriggeredRun):
|
|
|
131
132
|
)
|
|
132
133
|
|
|
133
134
|
command_obj = self.deployer.spm.get(pid)
|
|
135
|
+
command_obj.sync_wait()
|
|
134
136
|
return command_obj.process.returncode == 0
|
|
135
137
|
|
|
136
138
|
def terminate(self, **kwargs) -> bool:
|
|
@@ -165,6 +167,7 @@ class ArgoWorkflowsTriggeredRun(TriggeredRun):
|
|
|
165
167
|
)
|
|
166
168
|
|
|
167
169
|
command_obj = self.deployer.spm.get(pid)
|
|
170
|
+
command_obj.sync_wait()
|
|
168
171
|
return command_obj.process.returncode == 0
|
|
169
172
|
|
|
170
173
|
@property
|
|
@@ -319,6 +322,7 @@ class ArgoWorkflowsDeployedFlow(DeployedFlow):
|
|
|
319
322
|
)
|
|
320
323
|
|
|
321
324
|
command_obj = self.deployer.spm.get(pid)
|
|
325
|
+
command_obj.sync_wait()
|
|
322
326
|
return command_obj.process.returncode == 0
|
|
323
327
|
|
|
324
328
|
def trigger(self, **kwargs) -> ArgoWorkflowsTriggeredRun:
|
|
@@ -361,7 +365,7 @@ class ArgoWorkflowsDeployedFlow(DeployedFlow):
|
|
|
361
365
|
content = handle_timeout(
|
|
362
366
|
attribute_file_fd, command_obj, self.deployer.file_read_timeout
|
|
363
367
|
)
|
|
364
|
-
|
|
368
|
+
command_obj.sync_wait()
|
|
365
369
|
if command_obj.process.returncode == 0:
|
|
366
370
|
return ArgoWorkflowsTriggeredRun(
|
|
367
371
|
deployer=self.deployer, content=content
|
|
@@ -138,8 +138,8 @@ class BatchDecorator(StepDecorator):
|
|
|
138
138
|
supports_conda_environment = True
|
|
139
139
|
target_platform = "linux-64"
|
|
140
140
|
|
|
141
|
-
def
|
|
142
|
-
super(BatchDecorator, self).
|
|
141
|
+
def init(self):
|
|
142
|
+
super(BatchDecorator, self).init()
|
|
143
143
|
|
|
144
144
|
# If no docker image is explicitly specified, impute a default image.
|
|
145
145
|
if not self.attributes["image"]:
|
|
@@ -18,6 +18,7 @@ from metaflow.metaflow_config import (
|
|
|
18
18
|
SFN_S3_DISTRIBUTED_MAP_OUTPUT_PATH,
|
|
19
19
|
)
|
|
20
20
|
from metaflow.parameters import deploy_time_eval
|
|
21
|
+
from metaflow.user_configs.config_options import ConfigInput
|
|
21
22
|
from metaflow.util import dict_to_cli_options, to_pascalcase
|
|
22
23
|
|
|
23
24
|
from ..batch.batch import Batch
|
|
@@ -71,6 +72,7 @@ class StepFunctions(object):
|
|
|
71
72
|
self.username = username
|
|
72
73
|
self.max_workers = max_workers
|
|
73
74
|
self.workflow_timeout = workflow_timeout
|
|
75
|
+
self.config_parameters = self._process_config_parameters()
|
|
74
76
|
|
|
75
77
|
# https://aws.amazon.com/blogs/aws/step-functions-distributed-map-a-serverless-solution-for-large-scale-parallel-data-processing/
|
|
76
78
|
self.use_distributed_map = use_distributed_map
|
|
@@ -485,6 +487,10 @@ class StepFunctions(object):
|
|
|
485
487
|
"case-insensitive." % param.name
|
|
486
488
|
)
|
|
487
489
|
seen.add(norm)
|
|
490
|
+
# NOTE: We skip config parameters as these do not have dynamic values,
|
|
491
|
+
# and need to be treated differently.
|
|
492
|
+
if param.IS_CONFIG_PARAMETER:
|
|
493
|
+
continue
|
|
488
494
|
|
|
489
495
|
is_required = param.kwargs.get("required", False)
|
|
490
496
|
# Throw an exception if a schedule is set for a flow with required
|
|
@@ -501,6 +507,27 @@ class StepFunctions(object):
|
|
|
501
507
|
parameters.append(dict(name=param.name, value=value))
|
|
502
508
|
return parameters
|
|
503
509
|
|
|
510
|
+
def _process_config_parameters(self):
|
|
511
|
+
parameters = []
|
|
512
|
+
seen = set()
|
|
513
|
+
for var, param in self.flow._get_parameters():
|
|
514
|
+
if not param.IS_CONFIG_PARAMETER:
|
|
515
|
+
continue
|
|
516
|
+
# Throw an exception if the parameter is specified twice.
|
|
517
|
+
norm = param.name.lower()
|
|
518
|
+
if norm in seen:
|
|
519
|
+
raise MetaflowException(
|
|
520
|
+
"Parameter *%s* is specified twice. "
|
|
521
|
+
"Note that parameter names are "
|
|
522
|
+
"case-insensitive." % param.name
|
|
523
|
+
)
|
|
524
|
+
seen.add(norm)
|
|
525
|
+
|
|
526
|
+
parameters.append(
|
|
527
|
+
dict(name=param.name, kv_name=ConfigInput.make_key_name(param.name))
|
|
528
|
+
)
|
|
529
|
+
return parameters
|
|
530
|
+
|
|
504
531
|
def _batch(self, node):
|
|
505
532
|
attrs = {
|
|
506
533
|
# metaflow.user is only used for setting the AWS Job Name.
|
|
@@ -747,6 +774,11 @@ class StepFunctions(object):
|
|
|
747
774
|
metaflow_version["production_token"] = self.production_token
|
|
748
775
|
env["METAFLOW_VERSION"] = json.dumps(metaflow_version)
|
|
749
776
|
|
|
777
|
+
# map config values
|
|
778
|
+
cfg_env = {param["name"]: param["kv_name"] for param in self.config_parameters}
|
|
779
|
+
if cfg_env:
|
|
780
|
+
env["METAFLOW_FLOW_CONFIG_VALUE"] = json.dumps(cfg_env)
|
|
781
|
+
|
|
750
782
|
# Set AWS DynamoDb Table Name for state tracking for for-eaches.
|
|
751
783
|
# There are three instances when metaflow runtime directly interacts
|
|
752
784
|
# with AWS DynamoDB.
|
|
@@ -326,6 +326,7 @@ def make_flow(
|
|
|
326
326
|
|
|
327
327
|
# Attach AWS Batch decorator to the flow
|
|
328
328
|
decorators._attach_decorators(obj.flow, [BatchDecorator.name])
|
|
329
|
+
decorators._init(obj.flow)
|
|
329
330
|
decorators._init_step_decorators(
|
|
330
331
|
obj.flow, obj.graph, obj.environment, obj.flow_datastore, obj.logger
|
|
331
332
|
)
|
|
@@ -46,6 +46,7 @@ class StepFunctionsTriggeredRun(TriggeredRun):
|
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
command_obj = self.deployer.spm.get(pid)
|
|
49
|
+
command_obj.sync_wait()
|
|
49
50
|
return command_obj.process.returncode == 0
|
|
50
51
|
|
|
51
52
|
|
|
@@ -174,6 +175,7 @@ class StepFunctionsDeployedFlow(DeployedFlow):
|
|
|
174
175
|
)
|
|
175
176
|
|
|
176
177
|
command_obj = self.deployer.spm.get(pid)
|
|
178
|
+
command_obj.sync_wait()
|
|
177
179
|
return command_obj.process.returncode == 0
|
|
178
180
|
|
|
179
181
|
def trigger(self, **kwargs) -> StepFunctionsTriggeredRun:
|
|
@@ -217,6 +219,7 @@ class StepFunctionsDeployedFlow(DeployedFlow):
|
|
|
217
219
|
attribute_file_fd, command_obj, self.deployer.file_read_timeout
|
|
218
220
|
)
|
|
219
221
|
|
|
222
|
+
command_obj.sync_wait()
|
|
220
223
|
if command_obj.process.returncode == 0:
|
|
221
224
|
return StepFunctionsTriggeredRun(
|
|
222
225
|
deployer=self.deployer, content=content
|
|
@@ -722,8 +722,8 @@ def cli():
|
|
|
722
722
|
pass
|
|
723
723
|
|
|
724
724
|
|
|
725
|
-
@tracing.cli_entrypoint("s3op/list")
|
|
726
725
|
@cli.command("list", help="List S3 objects")
|
|
726
|
+
@tracing.cli_entrypoint("s3op/list")
|
|
727
727
|
@click.option(
|
|
728
728
|
"--recursive/--no-recursive",
|
|
729
729
|
default=False,
|
|
@@ -782,8 +782,8 @@ def lst(
|
|
|
782
782
|
print(format_result_line(idx, url.prefix, url.url, str(size)))
|
|
783
783
|
|
|
784
784
|
|
|
785
|
-
@tracing.cli_entrypoint("s3op/put")
|
|
786
785
|
@cli.command(help="Upload files to S3")
|
|
786
|
+
@tracing.cli_entrypoint("s3op/put")
|
|
787
787
|
@click.option(
|
|
788
788
|
"--file",
|
|
789
789
|
"files",
|
|
@@ -977,8 +977,8 @@ def _populate_prefixes(prefixes, inputs):
|
|
|
977
977
|
return prefixes, is_transient_retry
|
|
978
978
|
|
|
979
979
|
|
|
980
|
-
@tracing.cli_entrypoint("s3op/get")
|
|
981
980
|
@cli.command(help="Download files from S3")
|
|
981
|
+
@tracing.cli_entrypoint("s3op/get")
|
|
982
982
|
@click.option(
|
|
983
983
|
"--recursive/--no-recursive",
|
|
984
984
|
default=False,
|
|
@@ -314,7 +314,7 @@ class Kubernetes(object):
|
|
|
314
314
|
jobset.secret(k)
|
|
315
315
|
|
|
316
316
|
initial_configs = init_config()
|
|
317
|
-
for entry in ["OBP_PERIMETER", "
|
|
317
|
+
for entry in ["OBP_PERIMETER", "OBP_INTEGRATIONS_URL"]:
|
|
318
318
|
if entry not in initial_configs:
|
|
319
319
|
raise KubernetesException(
|
|
320
320
|
f"{entry} was not found in metaflow config. Please make sure to run `outerbounds configure <...>` command which can be found on the Ourebounds UI or reach out to your Outerbounds support team."
|
|
@@ -322,8 +322,8 @@ class Kubernetes(object):
|
|
|
322
322
|
|
|
323
323
|
additional_obp_configs = {
|
|
324
324
|
"OBP_PERIMETER": initial_configs["OBP_PERIMETER"],
|
|
325
|
-
"
|
|
326
|
-
"
|
|
325
|
+
"OBP_INTEGRATIONS_URL": initial_configs[
|
|
326
|
+
"OBP_INTEGRATIONS_URL"
|
|
327
327
|
],
|
|
328
328
|
}
|
|
329
329
|
for k, v in additional_obp_configs.items():
|
|
@@ -33,12 +33,12 @@ def kubernetes():
|
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
@tracing.cli_entrypoint("kubernetes/step")
|
|
37
36
|
@kubernetes.command(
|
|
38
37
|
help="Execute a single task on Kubernetes. This command calls the top-level step "
|
|
39
38
|
"command inside a Kubernetes pod with the given options. Typically you do not call "
|
|
40
39
|
"this command directly; it is used internally by Metaflow."
|
|
41
40
|
)
|
|
41
|
+
@tracing.cli_entrypoint("kubernetes/step")
|
|
42
42
|
@click.argument("step-name")
|
|
43
43
|
@click.argument("code-package-sha")
|
|
44
44
|
@click.argument("code-package-url")
|
|
@@ -153,8 +153,8 @@ class KubernetesDecorator(StepDecorator):
|
|
|
153
153
|
supports_conda_environment = True
|
|
154
154
|
target_platform = "linux-64"
|
|
155
155
|
|
|
156
|
-
def
|
|
157
|
-
super(KubernetesDecorator, self).
|
|
156
|
+
def init(self):
|
|
157
|
+
super(KubernetesDecorator, self).init()
|
|
158
158
|
|
|
159
159
|
if not self.attributes["namespace"]:
|
|
160
160
|
self.attributes["namespace"] = KUBERNETES_NAMESPACE
|
|
@@ -84,7 +84,7 @@ class KubernetesJob(object):
|
|
|
84
84
|
self._kwargs["disk"],
|
|
85
85
|
)
|
|
86
86
|
initial_configs = init_config()
|
|
87
|
-
for entry in ["OBP_PERIMETER", "
|
|
87
|
+
for entry in ["OBP_PERIMETER", "OBP_INTEGRATIONS_URL"]:
|
|
88
88
|
if entry not in initial_configs:
|
|
89
89
|
raise KubernetesJobException(
|
|
90
90
|
f"{entry} was not found in metaflow config. Please make sure to run `outerbounds configure <...>` command which can be found on the Ourebounds UI or reach out to your Outerbounds support team."
|
|
@@ -92,8 +92,8 @@ class KubernetesJob(object):
|
|
|
92
92
|
|
|
93
93
|
additional_obp_configs = {
|
|
94
94
|
"OBP_PERIMETER": initial_configs["OBP_PERIMETER"],
|
|
95
|
-
"
|
|
96
|
-
"
|
|
95
|
+
"OBP_INTEGRATIONS_URL": initial_configs[
|
|
96
|
+
"OBP_INTEGRATIONS_URL"
|
|
97
97
|
],
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -50,20 +50,26 @@ class CondaStepDecorator(StepDecorator):
|
|
|
50
50
|
# conda channels, users can specify channel::package as the package name.
|
|
51
51
|
|
|
52
52
|
def __init__(self, attributes=None, statically_defined=False):
|
|
53
|
-
self.
|
|
54
|
-
attributes.
|
|
53
|
+
self._attributes_with_user_values = (
|
|
54
|
+
set(attributes.keys()) if attributes is not None else set()
|
|
55
55
|
)
|
|
56
|
+
|
|
56
57
|
super(CondaStepDecorator, self).__init__(attributes, statically_defined)
|
|
57
58
|
|
|
59
|
+
def init(self):
|
|
60
|
+
super(CondaStepDecorator, self).init()
|
|
61
|
+
|
|
58
62
|
# Support legacy 'libraries=' attribute for the decorator.
|
|
59
63
|
self.attributes["packages"] = {
|
|
60
64
|
**self.attributes["libraries"],
|
|
61
65
|
**self.attributes["packages"],
|
|
62
66
|
}
|
|
63
67
|
del self.attributes["libraries"]
|
|
68
|
+
if self.attributes["packages"]:
|
|
69
|
+
self._attributes_with_user_values.add("packages")
|
|
64
70
|
|
|
65
71
|
def is_attribute_user_defined(self, name):
|
|
66
|
-
return name in self.
|
|
72
|
+
return name in self._attributes_with_user_values
|
|
67
73
|
|
|
68
74
|
def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
|
|
69
75
|
# The init_environment hook for Environment creates the relevant virtual
|
|
@@ -83,10 +89,10 @@ class CondaStepDecorator(StepDecorator):
|
|
|
83
89
|
**super_attributes["packages"],
|
|
84
90
|
**self.attributes["packages"],
|
|
85
91
|
}
|
|
86
|
-
self.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
self._attributes_with_user_values.update(
|
|
93
|
+
conda_base._attributes_with_user_values
|
|
94
|
+
)
|
|
95
|
+
|
|
90
96
|
self.attributes["python"] = (
|
|
91
97
|
self.attributes["python"] or super_attributes["python"]
|
|
92
98
|
)
|
|
@@ -333,11 +339,15 @@ class CondaFlowDecorator(FlowDecorator):
|
|
|
333
339
|
}
|
|
334
340
|
|
|
335
341
|
def __init__(self, attributes=None, statically_defined=False):
|
|
336
|
-
self.
|
|
337
|
-
attributes.
|
|
342
|
+
self._attributes_with_user_values = (
|
|
343
|
+
set(attributes.keys()) if attributes is not None else set()
|
|
338
344
|
)
|
|
345
|
+
|
|
339
346
|
super(CondaFlowDecorator, self).__init__(attributes, statically_defined)
|
|
340
347
|
|
|
348
|
+
def init(self):
|
|
349
|
+
super(CondaFlowDecorator, self).init()
|
|
350
|
+
|
|
341
351
|
# Support legacy 'libraries=' attribute for the decorator.
|
|
342
352
|
self.attributes["packages"] = {
|
|
343
353
|
**self.attributes["libraries"],
|
|
@@ -348,7 +358,7 @@ class CondaFlowDecorator(FlowDecorator):
|
|
|
348
358
|
self.attributes["python"] = str(self.attributes["python"])
|
|
349
359
|
|
|
350
360
|
def is_attribute_user_defined(self, name):
|
|
351
|
-
return name in self.
|
|
361
|
+
return name in self._attributes_with_user_values
|
|
352
362
|
|
|
353
363
|
def flow_init(
|
|
354
364
|
self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
|
|
@@ -25,9 +25,10 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
25
25
|
defaults = {"packages": {}, "python": None, "disabled": None} # wheels
|
|
26
26
|
|
|
27
27
|
def __init__(self, attributes=None, statically_defined=False):
|
|
28
|
-
self.
|
|
29
|
-
attributes.
|
|
28
|
+
self._attributes_with_user_values = (
|
|
29
|
+
set(attributes.keys()) if attributes is not None else set()
|
|
30
30
|
)
|
|
31
|
+
|
|
31
32
|
super().__init__(attributes, statically_defined)
|
|
32
33
|
|
|
33
34
|
def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
|
|
@@ -42,10 +43,9 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
42
43
|
if "pypi_base" in self.flow._flow_decorators:
|
|
43
44
|
pypi_base = self.flow._flow_decorators["pypi_base"][0]
|
|
44
45
|
super_attributes = pypi_base.attributes
|
|
45
|
-
self.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
46
|
+
self._attributes_with_user_values.update(
|
|
47
|
+
pypi_base._attributes_with_user_values
|
|
48
|
+
)
|
|
49
49
|
self.attributes["packages"] = {
|
|
50
50
|
**super_attributes["packages"],
|
|
51
51
|
**self.attributes["packages"],
|
|
@@ -106,7 +106,7 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
106
106
|
environment.set_local_root(LocalStorage.get_datastore_root_from_config(logger))
|
|
107
107
|
|
|
108
108
|
def is_attribute_user_defined(self, name):
|
|
109
|
-
return name in self.
|
|
109
|
+
return name in self._attributes_with_user_values
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
class PyPIFlowDecorator(FlowDecorator):
|
|
@@ -129,9 +129,10 @@ class PyPIFlowDecorator(FlowDecorator):
|
|
|
129
129
|
defaults = {"packages": {}, "python": None, "disabled": None}
|
|
130
130
|
|
|
131
131
|
def __init__(self, attributes=None, statically_defined=False):
|
|
132
|
-
self.
|
|
133
|
-
attributes.
|
|
132
|
+
self._attributes_with_user_values = (
|
|
133
|
+
set(attributes.keys()) if attributes is not None else set()
|
|
134
134
|
)
|
|
135
|
+
|
|
135
136
|
super().__init__(attributes, statically_defined)
|
|
136
137
|
|
|
137
138
|
def flow_init(
|
|
@@ -140,6 +141,7 @@ class PyPIFlowDecorator(FlowDecorator):
|
|
|
140
141
|
from metaflow import decorators
|
|
141
142
|
|
|
142
143
|
decorators._attach_decorators(flow, ["pypi"])
|
|
144
|
+
decorators._init(flow)
|
|
143
145
|
|
|
144
146
|
# @pypi uses a conda environment to create a virtual environment.
|
|
145
147
|
# The conda environment can be created through micromamba.
|
|
@@ -37,8 +37,8 @@ class TimeoutDecorator(StepDecorator):
|
|
|
37
37
|
name = "timeout"
|
|
38
38
|
defaults = {"seconds": 0, "minutes": 0, "hours": 0}
|
|
39
39
|
|
|
40
|
-
def
|
|
41
|
-
super(
|
|
40
|
+
def init(self):
|
|
41
|
+
super().init()
|
|
42
42
|
# Initialize secs in __init__ so other decorators could safely use this
|
|
43
43
|
# value without worrying about decorator order.
|
|
44
44
|
# Convert values in attributes to type:int since they can be type:str
|
metaflow/runner/click_api.py
CHANGED
|
@@ -9,6 +9,7 @@ if sys.version_info < (3, 7):
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
import datetime
|
|
12
|
+
import functools
|
|
12
13
|
import importlib
|
|
13
14
|
import inspect
|
|
14
15
|
import itertools
|
|
@@ -38,6 +39,7 @@ from metaflow.decorators import add_decorator_options
|
|
|
38
39
|
from metaflow.exception import MetaflowException
|
|
39
40
|
from metaflow.includefile import FilePathClass
|
|
40
41
|
from metaflow.parameters import JSONTypeClass, flow_context
|
|
42
|
+
from metaflow.user_configs.config_options import LocalFileInput
|
|
41
43
|
|
|
42
44
|
# Define a recursive type alias for JSON
|
|
43
45
|
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
|
|
@@ -55,6 +57,7 @@ click_to_python_types = {
|
|
|
55
57
|
File: str,
|
|
56
58
|
JSONTypeClass: JSON,
|
|
57
59
|
FilePathClass: str,
|
|
60
|
+
LocalFileInput: str,
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
|
|
@@ -124,6 +127,37 @@ def _method_sanity_check(
|
|
|
124
127
|
return method_params
|
|
125
128
|
|
|
126
129
|
|
|
130
|
+
def _lazy_load_command(
|
|
131
|
+
cli_collection: click.Group,
|
|
132
|
+
flow_parameters: Union[str, List[Parameter]],
|
|
133
|
+
_self,
|
|
134
|
+
name: str,
|
|
135
|
+
):
|
|
136
|
+
|
|
137
|
+
# Context is not used in get_command so we can pass None. Since we pin click,
|
|
138
|
+
# this won't change from under us.
|
|
139
|
+
|
|
140
|
+
if isinstance(flow_parameters, str):
|
|
141
|
+
# Resolve flow_parameters -- for start, this is a function which we
|
|
142
|
+
# need to call to figure out the actual parameters (may be changed by configs)
|
|
143
|
+
flow_parameters = getattr(_self, flow_parameters)()
|
|
144
|
+
cmd_obj = cli_collection.get_command(None, name)
|
|
145
|
+
if cmd_obj:
|
|
146
|
+
if isinstance(cmd_obj, click.Group):
|
|
147
|
+
# TODO: possibly check for fake groups with cmd_obj.name in ["cli", "main"]
|
|
148
|
+
result = functools.partial(extract_group(cmd_obj, flow_parameters), _self)
|
|
149
|
+
elif isinstance(cmd_obj, click.Command):
|
|
150
|
+
result = functools.partial(extract_command(cmd_obj, flow_parameters), _self)
|
|
151
|
+
else:
|
|
152
|
+
raise RuntimeError(
|
|
153
|
+
"Cannot handle %s of type %s" % (cmd_obj.name, type(cmd_obj))
|
|
154
|
+
)
|
|
155
|
+
setattr(_self, name, result)
|
|
156
|
+
return result
|
|
157
|
+
else:
|
|
158
|
+
raise AttributeError()
|
|
159
|
+
|
|
160
|
+
|
|
127
161
|
def get_annotation(param: Union[click.Argument, click.Option]):
|
|
128
162
|
py_type = click_to_python_types[type(param.type)]
|
|
129
163
|
if not param.required:
|
|
@@ -179,9 +213,11 @@ def extract_flow_class_from_file(flow_file: str) -> FlowSpec:
|
|
|
179
213
|
|
|
180
214
|
|
|
181
215
|
class MetaflowAPI(object):
|
|
182
|
-
def __init__(self, parent=None, **kwargs):
|
|
216
|
+
def __init__(self, parent=None, flow_cls=None, **kwargs):
|
|
183
217
|
self._parent = parent
|
|
184
218
|
self._chain = [{self._API_NAME: kwargs}]
|
|
219
|
+
self._flow_cls = flow_cls
|
|
220
|
+
self._cached_computed_parameters = None
|
|
185
221
|
|
|
186
222
|
@property
|
|
187
223
|
def parent(self):
|
|
@@ -200,23 +236,22 @@ class MetaflowAPI(object):
|
|
|
200
236
|
@classmethod
|
|
201
237
|
def from_cli(cls, flow_file: str, cli_collection: Callable) -> Callable:
|
|
202
238
|
flow_cls = extract_flow_class_from_file(flow_file)
|
|
203
|
-
|
|
239
|
+
|
|
204
240
|
with flow_context(flow_cls) as _:
|
|
205
241
|
add_decorator_options(cli_collection)
|
|
206
242
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
243
|
+
def getattr_wrapper(_self, name):
|
|
244
|
+
# Functools.partial do not automatically bind self (no __get__)
|
|
245
|
+
return _self._internal_getattr(_self, name)
|
|
246
|
+
|
|
247
|
+
class_dict = {
|
|
248
|
+
"__module__": "metaflow",
|
|
249
|
+
"_API_NAME": flow_file,
|
|
250
|
+
"_internal_getattr": functools.partial(
|
|
251
|
+
_lazy_load_command, cli_collection, "_compute_flow_parameters"
|
|
252
|
+
),
|
|
253
|
+
"__getattr__": getattr_wrapper,
|
|
254
|
+
}
|
|
220
255
|
|
|
221
256
|
to_return = type(flow_file, (MetaflowAPI,), class_dict)
|
|
222
257
|
to_return.__name__ = flow_file
|
|
@@ -237,11 +272,11 @@ class MetaflowAPI(object):
|
|
|
237
272
|
defaults,
|
|
238
273
|
**kwargs,
|
|
239
274
|
)
|
|
240
|
-
return to_return(parent=None, **method_params)
|
|
275
|
+
return to_return(parent=None, flow_cls=flow_cls, **method_params)
|
|
241
276
|
|
|
242
277
|
m = _method
|
|
243
|
-
m.__name__ =
|
|
244
|
-
m.__doc__ = getattr(
|
|
278
|
+
m.__name__ = cli_collection.name
|
|
279
|
+
m.__doc__ = getattr(cli_collection, "help", None)
|
|
245
280
|
m.__signature__ = inspect.signature(_method).replace(
|
|
246
281
|
parameters=params_sigs.values()
|
|
247
282
|
)
|
|
@@ -287,6 +322,25 @@ class MetaflowAPI(object):
|
|
|
287
322
|
|
|
288
323
|
return components
|
|
289
324
|
|
|
325
|
+
def _compute_flow_parameters(self):
|
|
326
|
+
if self._flow_cls is None or self._parent is not None:
|
|
327
|
+
raise RuntimeError(
|
|
328
|
+
"Computing flow-level parameters for a non start API. "
|
|
329
|
+
"Please report to the Metaflow team."
|
|
330
|
+
)
|
|
331
|
+
# TODO: We need to actually compute the new parameters (based on configs) which
|
|
332
|
+
# would involve processing the options at least partially. We will do this
|
|
333
|
+
# before GA but for now making it work for regular parameters
|
|
334
|
+
if self._cached_computed_parameters is not None:
|
|
335
|
+
return self._cached_computed_parameters
|
|
336
|
+
self._cached_computed_parameters = []
|
|
337
|
+
for _, param in self._flow_cls._get_parameters():
|
|
338
|
+
if param.IS_CONFIG_PARAMETER:
|
|
339
|
+
continue
|
|
340
|
+
param.init()
|
|
341
|
+
self._cached_computed_parameters.append(param)
|
|
342
|
+
return self._cached_computed_parameters
|
|
343
|
+
|
|
290
344
|
|
|
291
345
|
def extract_all_params(cmd_obj: Union[click.Command, click.Group]):
|
|
292
346
|
arg_params_sigs = OrderedDict()
|
|
@@ -351,7 +405,7 @@ def extract_group(cmd_obj: click.Group, flow_parameters: List[Parameter]) -> Cal
|
|
|
351
405
|
method_params = _method_sanity_check(
|
|
352
406
|
possible_arg_params, possible_opt_params, annotations, defaults, **kwargs
|
|
353
407
|
)
|
|
354
|
-
return resulting_class(parent=_self, **method_params)
|
|
408
|
+
return resulting_class(parent=_self, flow_cls=None, **method_params)
|
|
355
409
|
|
|
356
410
|
m = _method
|
|
357
411
|
m.__name__ = cmd_obj.name
|