mage-ai 0.8.78__py3-none-any.whl → 0.8.80__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 mage-ai might be problematic. Click here for more details.
- mage_ai/api/policies/PipelineRunPolicy.py +1 -0
- mage_ai/api/policies/StatusPolicy.py +4 -2
- mage_ai/api/presenters/StatusPresenter.py +1 -0
- mage_ai/api/resources/LogResource.py +12 -3
- mage_ai/api/resources/StatusResource.py +2 -0
- mage_ai/data_preparation/executors/streaming_pipeline_executor.py +73 -57
- mage_ai/data_preparation/models/block/__init__.py +30 -7
- mage_ai/data_preparation/models/constants.py +2 -0
- mage_ai/data_preparation/models/widget/charts.py +23 -4
- mage_ai/orchestration/db/models/schedules.py +2 -0
- mage_ai/orchestration/notification/sender.py +18 -5
- mage_ai/orchestration/pipeline_scheduler.py +26 -9
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/404.html.html +2 -2
- mage_ai/server/frontend_dist/_next/static/{m8Ltx9sPofwrShNBHkhe- → K62oaHK5x3k16vVxdvIWf}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/5682-c0d87b28bf381aae.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/8312-71137409aea5d028.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/8957-6edafc5a2521efdf.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-d90d32812b2be89e.js +1 -0
- mage_ai/server/frontend_dist/index.html +2 -2
- mage_ai/server/frontend_dist/manage.html +2 -2
- mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist/pipelines.html +2 -2
- mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist/settings.html +2 -2
- mage_ai/server/frontend_dist/sign-in.html +2 -2
- mage_ai/server/frontend_dist/terminal.html +2 -2
- mage_ai/server/frontend_dist/test.html +3 -3
- mage_ai/server/frontend_dist/triggers.html +2 -2
- mage_ai/server/kernel_output_parser.py +5 -0
- mage_ai/services/gcp/cloud_run/cloud_run.py +37 -18
- mage_ai/services/gcp/cloud_run/config.py +2 -1
- mage_ai/tests/orchestration/db/test_models.py +21 -10
- mage_ai/tests/orchestration/notification/test_sender.py +6 -4
- mage_ai/usage_statistics/logger.py +2 -0
- {mage_ai-0.8.78.dist-info → mage_ai-0.8.80.dist-info}/METADATA +1 -1
- {mage_ai-0.8.78.dist-info → mage_ai-0.8.80.dist-info}/RECORD +61 -61
- mage_ai/server/frontend_dist/_next/static/chunks/5682-83f92c7d6ec974f5.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/8312-3bb169298804c401.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/8957-0c39a705c8215858.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-22d76e8fa386ba37.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{m8Ltx9sPofwrShNBHkhe- → K62oaHK5x3k16vVxdvIWf}/_middlewareManifest.js +0 -0
- /mage_ai/server/frontend_dist/_next/static/{m8Ltx9sPofwrShNBHkhe- → K62oaHK5x3k16vVxdvIWf}/_ssgManifest.js +0 -0
- {mage_ai-0.8.78.dist-info → mage_ai-0.8.80.dist-info}/LICENSE +0 -0
- {mage_ai-0.8.78.dist-info → mage_ai-0.8.80.dist-info}/WHEEL +0 -0
- {mage_ai-0.8.78.dist-info → mage_ai-0.8.80.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.8.78.dist-info → mage_ai-0.8.80.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,12 @@ StatusPolicy.allow_actions([
|
|
|
12
12
|
constants.LIST,
|
|
13
13
|
], scopes=[
|
|
14
14
|
OauthScope.CLIENT_PRIVATE,
|
|
15
|
-
|
|
15
|
+
OauthScope.CLIENT_PUBLIC,
|
|
16
|
+
])
|
|
16
17
|
|
|
17
18
|
StatusPolicy.allow_read(StatusPresenter.default_attributes, scopes=[
|
|
18
19
|
OauthScope.CLIENT_PRIVATE,
|
|
20
|
+
OauthScope.CLIENT_PUBLIC,
|
|
19
21
|
], on_action=[
|
|
20
22
|
constants.LIST,
|
|
21
|
-
]
|
|
23
|
+
])
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from sqlalchemy.orm import aliased
|
|
5
|
+
|
|
2
6
|
from mage_ai.api.errors import ApiError
|
|
3
7
|
from mage_ai.api.operations.constants import META_KEY_LIMIT
|
|
4
8
|
from mage_ai.api.resources.GenericResource import GenericResource
|
|
5
9
|
from mage_ai.data_preparation.models.pipeline import Pipeline
|
|
6
10
|
from mage_ai.orchestration.db import safe_db_query
|
|
7
|
-
from mage_ai.orchestration.db.models.schedules import
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
from mage_ai.orchestration.db.models.schedules import (
|
|
12
|
+
BlockRun,
|
|
13
|
+
PipelineRun,
|
|
14
|
+
PipelineSchedule,
|
|
15
|
+
)
|
|
10
16
|
|
|
11
17
|
MAX_LOG_FILES = 20
|
|
12
18
|
|
|
@@ -96,6 +102,7 @@ class LogResource(GenericResource):
|
|
|
96
102
|
a.pipeline_schedule_id,
|
|
97
103
|
a.pipeline_schedule_id,
|
|
98
104
|
a.pipeline_uuid,
|
|
105
|
+
a.variables,
|
|
99
106
|
]
|
|
100
107
|
|
|
101
108
|
total_pipeline_run_log_count = 0
|
|
@@ -155,6 +162,7 @@ class LogResource(GenericResource):
|
|
|
155
162
|
model.execution_date = row.execution_date
|
|
156
163
|
model.pipeline_schedule_id = row.pipeline_schedule_id
|
|
157
164
|
model.pipeline_uuid = row.pipeline_uuid
|
|
165
|
+
model.variables = row.variables
|
|
158
166
|
logs = await model.logs_async()
|
|
159
167
|
pipeline_log_file_path = logs.get('path')
|
|
160
168
|
if pipeline_log_file_path not in processed_pipeline_run_log_files:
|
|
@@ -235,6 +243,7 @@ class LogResource(GenericResource):
|
|
|
235
243
|
model.execution_date = row.execution_date
|
|
236
244
|
model.pipeline_schedule_id = row.pipeline_schedule_id
|
|
237
245
|
model.pipeline_uuid = row.pipeline_uuid
|
|
246
|
+
model.variables = row.variables
|
|
238
247
|
|
|
239
248
|
model2 = BlockRun()
|
|
240
249
|
model2.block_uuid = row.block_uuid
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
from mage_ai.api.resources.GenericResource import GenericResource
|
|
4
|
+
from mage_ai.data_preparation.models.constants import MAX_PRINT_OUTPUT_LINES
|
|
4
5
|
from mage_ai.data_preparation.repo_manager import (
|
|
5
6
|
ProjectType,
|
|
6
7
|
get_project_type,
|
|
@@ -53,6 +54,7 @@ class StatusResource(GenericResource):
|
|
|
53
54
|
'scheduler_status': scheduler_manager.get_status(),
|
|
54
55
|
'instance_type': instance_type,
|
|
55
56
|
'disable_pipeline_edit_access': is_disable_pipeline_edit_access(),
|
|
57
|
+
'max_print_output_lines': MAX_PRINT_OUTPUT_LINES,
|
|
56
58
|
'require_user_authentication': REQUIRE_USER_AUTHENTICATION,
|
|
57
59
|
}
|
|
58
60
|
return self.build_result_set([status], user, **kwargs)
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import copy
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
1
5
|
from contextlib import redirect_stderr, redirect_stdout
|
|
6
|
+
from typing import Callable, Dict, List, Union
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
2
10
|
from mage_ai.data_preparation.executors.pipeline_executor import PipelineExecutor
|
|
3
11
|
from mage_ai.data_preparation.logging.logger import DictLogger
|
|
4
12
|
from mage_ai.data_preparation.models.constants import BlockType
|
|
5
13
|
from mage_ai.data_preparation.models.pipeline import Pipeline
|
|
6
14
|
from mage_ai.data_preparation.shared.stream import StreamToLogger
|
|
7
15
|
from mage_ai.shared.hash import merge_dict
|
|
8
|
-
from typing import Callable, Dict, List, Union
|
|
9
|
-
import asyncio
|
|
10
|
-
import logging
|
|
11
|
-
import os
|
|
12
|
-
import yaml
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class StreamingPipelineExecutor(PipelineExecutor):
|
|
@@ -21,7 +24,9 @@ class StreamingPipelineExecutor(PipelineExecutor):
|
|
|
21
24
|
def parse_and_validate_blocks(self):
|
|
22
25
|
"""
|
|
23
26
|
Find the first valid streaming pipeline is in the structure:
|
|
24
|
-
source ->
|
|
27
|
+
source -> transformer1 -> sink2
|
|
28
|
+
-> transformer2 -> sink2
|
|
29
|
+
-> transformer3 -> sink3
|
|
25
30
|
"""
|
|
26
31
|
blocks = self.pipeline.blocks_by_uuid.values()
|
|
27
32
|
source_blocks = []
|
|
@@ -29,24 +34,23 @@ class StreamingPipelineExecutor(PipelineExecutor):
|
|
|
29
34
|
transformer_blocks = []
|
|
30
35
|
for b in blocks:
|
|
31
36
|
if b.type == BlockType.DATA_LOADER:
|
|
37
|
+
# Data loader block should be root block
|
|
32
38
|
if len(b.upstream_blocks or []) > 0:
|
|
33
39
|
raise Exception(f'Data loader {b.uuid} can\'t have upstream blocks.')
|
|
34
|
-
if len(b.downstream_blocks or [])
|
|
35
|
-
raise Exception(f'Data loader {b.uuid} must have one transformer or
|
|
36
|
-
' exporter as the downstream block.')
|
|
40
|
+
if len(b.downstream_blocks or []) < 1:
|
|
41
|
+
raise Exception(f'Data loader {b.uuid} must have at least one transformer or'
|
|
42
|
+
' data exporter as the downstream block.')
|
|
37
43
|
source_blocks.append(b)
|
|
38
44
|
if b.type == BlockType.DATA_EXPORTER:
|
|
45
|
+
# Data exporter block should be leaf block
|
|
39
46
|
if len(b.downstream_blocks or []) > 0:
|
|
40
47
|
raise Exception(f'Data expoter {b.uuid} can\'t have downstream blocks.')
|
|
41
48
|
if len(b.upstream_blocks or []) != 1:
|
|
42
|
-
raise Exception(f'Data
|
|
43
|
-
'
|
|
49
|
+
raise Exception(f'Data exporter {b.uuid} must have a transformer or data'
|
|
50
|
+
' loader as the upstream block.')
|
|
44
51
|
sink_blocks.append(b)
|
|
45
52
|
if b.type == BlockType.TRANSFORMER:
|
|
46
|
-
|
|
47
|
-
raise Exception(
|
|
48
|
-
f'Transformer {b.uuid} should (only) have one downstream block.',
|
|
49
|
-
)
|
|
53
|
+
# Each transformer block can only have one upstream block
|
|
50
54
|
if len(b.upstream_blocks or []) != 1:
|
|
51
55
|
raise Exception(f'Transformer {b.uuid} should (only) have one upstream block.')
|
|
52
56
|
transformer_blocks.append(b)
|
|
@@ -54,15 +58,8 @@ class StreamingPipelineExecutor(PipelineExecutor):
|
|
|
54
58
|
if len(source_blocks) != 1:
|
|
55
59
|
raise Exception('Please provide (only) one data loader block as the source.')
|
|
56
60
|
|
|
57
|
-
if len(transformer_blocks) > 1:
|
|
58
|
-
raise Exception('Please provide no more than one transformer block.')
|
|
59
|
-
|
|
60
|
-
if len(sink_blocks) != 1:
|
|
61
|
-
raise Exception('Please provide (only) one data expoter block as the sink.')
|
|
62
|
-
|
|
63
61
|
self.source_block = source_blocks[0]
|
|
64
|
-
self.
|
|
65
|
-
self.transformer_block = transformer_blocks[0] if len(transformer_blocks) > 0 else None
|
|
62
|
+
self.sink_blocks = sink_blocks
|
|
66
63
|
|
|
67
64
|
def execute(
|
|
68
65
|
self,
|
|
@@ -97,11 +94,10 @@ class StreamingPipelineExecutor(PipelineExecutor):
|
|
|
97
94
|
raise e
|
|
98
95
|
|
|
99
96
|
def __execute_in_python(self, build_block_output_stdout: Callable[..., object] = None):
|
|
97
|
+
from mage_ai.streaming.sinks.sink_factory import SinkFactory
|
|
100
98
|
from mage_ai.streaming.sources.base import SourceConsumeMethod
|
|
101
99
|
from mage_ai.streaming.sources.source_factory import SourceFactory
|
|
102
|
-
from mage_ai.streaming.sinks.sink_factory import SinkFactory
|
|
103
100
|
source_config = yaml.safe_load(self.source_block.content)
|
|
104
|
-
sink_config = yaml.safe_load(self.sink_block.content)
|
|
105
101
|
source = SourceFactory.get_source(
|
|
106
102
|
source_config,
|
|
107
103
|
checkpoint_path=os.path.join(
|
|
@@ -109,47 +105,67 @@ class StreamingPipelineExecutor(PipelineExecutor):
|
|
|
109
105
|
'streaming_checkpoint',
|
|
110
106
|
),
|
|
111
107
|
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
108
|
+
|
|
109
|
+
sinks_by_uuid = dict()
|
|
110
|
+
for sink_block in self.sink_blocks:
|
|
111
|
+
sink_config = yaml.safe_load(sink_block.content)
|
|
112
|
+
sinks_by_uuid[sink_block.uuid] = SinkFactory.get_sink(
|
|
113
|
+
sink_config,
|
|
114
|
+
buffer_path=os.path.join(
|
|
115
|
+
self.pipeline.pipeline_variables_dir,
|
|
116
|
+
'buffer',
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def handle_batch_events_recursively(curr_block, outputs_by_block: Dict, **kwargs):
|
|
121
|
+
curr_block_output = outputs_by_block[curr_block.uuid]
|
|
122
|
+
for downstream_block in curr_block.downstream_blocks:
|
|
123
|
+
if downstream_block.type == BlockType.TRANSFORMER:
|
|
124
|
+
execute_block_kwargs = dict(
|
|
125
|
+
global_vars=kwargs,
|
|
126
|
+
input_args=[copy.deepcopy(curr_block_output)],
|
|
127
|
+
logger=self.logger,
|
|
128
|
+
)
|
|
129
|
+
if build_block_output_stdout:
|
|
130
|
+
execute_block_kwargs['build_block_output_stdout'] = \
|
|
131
|
+
build_block_output_stdout
|
|
132
|
+
outputs_by_block[downstream_block.uuid] = \
|
|
133
|
+
downstream_block.execute_block(
|
|
134
|
+
**execute_block_kwargs,
|
|
135
|
+
)['output']
|
|
136
|
+
elif downstream_block.type == BlockType.DATA_EXPORTER:
|
|
137
|
+
sinks_by_uuid[downstream_block.uuid].batch_write(
|
|
138
|
+
copy.deepcopy(curr_block_output))
|
|
139
|
+
if downstream_block.downstream_blocks:
|
|
140
|
+
handle_batch_events_recursively(
|
|
141
|
+
downstream_block,
|
|
142
|
+
outputs_by_block,
|
|
143
|
+
**kwargs,
|
|
144
|
+
)
|
|
119
145
|
|
|
120
146
|
def handle_batch_events(messages: List[Union[Dict, str]], **kwargs):
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if build_block_output_stdout:
|
|
128
|
-
execute_block_kwargs['build_block_output_stdout'] = build_block_output_stdout
|
|
129
|
-
messages = self.transformer_block.execute_block(
|
|
130
|
-
**execute_block_kwargs,
|
|
131
|
-
)['output']
|
|
132
|
-
sink.batch_write(messages)
|
|
147
|
+
# Handle the events with DFS
|
|
148
|
+
|
|
149
|
+
outputs_by_block = dict()
|
|
150
|
+
outputs_by_block[self.source_block.uuid] = messages
|
|
151
|
+
|
|
152
|
+
handle_batch_events_recursively(self.source_block, outputs_by_block)
|
|
133
153
|
|
|
134
154
|
async def handle_event_async(message, **kwargs):
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
logger=self.logger,
|
|
140
|
-
)
|
|
141
|
-
if build_block_output_stdout:
|
|
142
|
-
execute_block_kwargs['build_block_output_stdout'] = build_block_output_stdout
|
|
143
|
-
messages = self.transformer_block.execute_block(
|
|
144
|
-
**execute_block_kwargs,
|
|
145
|
-
)['output']
|
|
146
|
-
sink.batch_write(messages)
|
|
155
|
+
outputs_by_block = dict()
|
|
156
|
+
outputs_by_block[self.source_block.uuid] = [message]
|
|
157
|
+
|
|
158
|
+
handle_batch_events_recursively(self.source_block, outputs_by_block)
|
|
147
159
|
|
|
148
160
|
# Long running method
|
|
149
161
|
if source.consume_method == SourceConsumeMethod.BATCH_READ:
|
|
150
162
|
source.batch_read(handler=handle_batch_events)
|
|
151
163
|
elif source.consume_method == SourceConsumeMethod.READ_ASYNC:
|
|
152
|
-
asyncio.
|
|
164
|
+
loop = asyncio.get_event_loop()
|
|
165
|
+
if loop is not None:
|
|
166
|
+
loop.run_until_complete(source.read_async(handler=handle_event_async))
|
|
167
|
+
else:
|
|
168
|
+
asyncio.run(source.read_async(handler=handle_event_async))
|
|
153
169
|
|
|
154
170
|
def __excute_in_flink(self):
|
|
155
171
|
"""
|
|
@@ -227,9 +227,11 @@ class Block:
|
|
|
227
227
|
status: BlockStatus = BlockStatus.NOT_EXECUTED,
|
|
228
228
|
pipeline=None,
|
|
229
229
|
language: BlockLanguage = BlockLanguage.PYTHON,
|
|
230
|
-
configuration: Dict =
|
|
230
|
+
configuration: Dict = None,
|
|
231
231
|
has_callback: bool = False,
|
|
232
232
|
):
|
|
233
|
+
if configuration is None:
|
|
234
|
+
configuration = dict()
|
|
233
235
|
self.name = name or uuid
|
|
234
236
|
self._uuid = uuid
|
|
235
237
|
self.type = block_type
|
|
@@ -614,7 +616,7 @@ class Block:
|
|
|
614
616
|
self,
|
|
615
617
|
global_vars: Dict = None,
|
|
616
618
|
logger: Logger = None,
|
|
617
|
-
logging_tags: Dict =
|
|
619
|
+
logging_tags: Dict = None,
|
|
618
620
|
**kwargs
|
|
619
621
|
):
|
|
620
622
|
"""
|
|
@@ -623,6 +625,9 @@ class Block:
|
|
|
623
625
|
websocket as a way to test the code in the callback. To run a block in a pipeline
|
|
624
626
|
run, use a BlockExecutor.
|
|
625
627
|
"""
|
|
628
|
+
if logging_tags is None:
|
|
629
|
+
logging_tags = dict()
|
|
630
|
+
|
|
626
631
|
arr = []
|
|
627
632
|
if self.callback_block:
|
|
628
633
|
arr.append(self.callback_block)
|
|
@@ -643,6 +648,7 @@ class Block:
|
|
|
643
648
|
global_vars=global_vars,
|
|
644
649
|
logger=logger,
|
|
645
650
|
logging_tags=logging_tags,
|
|
651
|
+
parent_block=self,
|
|
646
652
|
)
|
|
647
653
|
raise e
|
|
648
654
|
|
|
@@ -652,6 +658,7 @@ class Block:
|
|
|
652
658
|
global_vars=global_vars,
|
|
653
659
|
logger=logger,
|
|
654
660
|
logging_tags=logging_tags,
|
|
661
|
+
parent_block=self,
|
|
655
662
|
)
|
|
656
663
|
|
|
657
664
|
return output
|
|
@@ -664,7 +671,7 @@ class Block:
|
|
|
664
671
|
execution_partition: str = None,
|
|
665
672
|
global_vars: Dict = None,
|
|
666
673
|
logger: Logger = None,
|
|
667
|
-
logging_tags: Dict =
|
|
674
|
+
logging_tags: Dict = None,
|
|
668
675
|
run_all_blocks: bool = False,
|
|
669
676
|
test_execution: bool = False,
|
|
670
677
|
update_status: bool = True,
|
|
@@ -678,6 +685,9 @@ class Block:
|
|
|
678
685
|
run_settings: Dict = None,
|
|
679
686
|
**kwargs,
|
|
680
687
|
) -> Dict:
|
|
688
|
+
if logging_tags is None:
|
|
689
|
+
logging_tags = dict()
|
|
690
|
+
|
|
681
691
|
try:
|
|
682
692
|
if not run_all_blocks:
|
|
683
693
|
not_executed_upstream_blocks = list(
|
|
@@ -873,7 +883,7 @@ class Block:
|
|
|
873
883
|
execution_partition: str = None,
|
|
874
884
|
input_args: List = None,
|
|
875
885
|
logger: Logger = None,
|
|
876
|
-
logging_tags: Dict =
|
|
886
|
+
logging_tags: Dict = None,
|
|
877
887
|
global_vars: Dict = None,
|
|
878
888
|
test_execution: bool = False,
|
|
879
889
|
input_from_output: Dict = None,
|
|
@@ -883,6 +893,9 @@ class Block:
|
|
|
883
893
|
run_settings: Dict = None,
|
|
884
894
|
**kwargs,
|
|
885
895
|
) -> Dict:
|
|
896
|
+
if logging_tags is None:
|
|
897
|
+
logging_tags = dict()
|
|
898
|
+
|
|
886
899
|
# Add pipeline uuid and block uuid to global_vars
|
|
887
900
|
global_vars = merge_dict(
|
|
888
901
|
global_vars or dict(),
|
|
@@ -951,7 +964,7 @@ class Block:
|
|
|
951
964
|
execution_partition: str = None,
|
|
952
965
|
input_vars: List = None,
|
|
953
966
|
logger: Logger = None,
|
|
954
|
-
logging_tags: Dict =
|
|
967
|
+
logging_tags: Dict = None,
|
|
955
968
|
global_vars: Dict = None,
|
|
956
969
|
test_execution: bool = False,
|
|
957
970
|
input_from_output: Dict = None,
|
|
@@ -960,6 +973,9 @@ class Block:
|
|
|
960
973
|
run_settings: Dict = None,
|
|
961
974
|
**kwargs,
|
|
962
975
|
) -> List:
|
|
976
|
+
if logging_tags is None:
|
|
977
|
+
logging_tags = dict()
|
|
978
|
+
|
|
963
979
|
decorated_functions = []
|
|
964
980
|
test_functions = []
|
|
965
981
|
|
|
@@ -1531,13 +1547,18 @@ df = get_variable('{self.pipeline.uuid}', '{block_uuid}', 'df')
|
|
|
1531
1547
|
build_block_output_stdout: Callable[..., object] = None,
|
|
1532
1548
|
custom_code: str = None,
|
|
1533
1549
|
execution_partition: str = None,
|
|
1534
|
-
global_vars: Dict =
|
|
1550
|
+
global_vars: Dict = None,
|
|
1535
1551
|
logger: Logger = None,
|
|
1536
|
-
logging_tags: Dict =
|
|
1552
|
+
logging_tags: Dict = None,
|
|
1537
1553
|
update_tests: bool = True,
|
|
1538
1554
|
dynamic_block_uuid: str = None,
|
|
1539
1555
|
from_notebook: bool = False,
|
|
1540
1556
|
) -> None:
|
|
1557
|
+
if global_vars is None:
|
|
1558
|
+
global_vars = dict()
|
|
1559
|
+
if logging_tags is None:
|
|
1560
|
+
logging_tags = dict()
|
|
1561
|
+
|
|
1541
1562
|
self.dynamic_block_uuid = dynamic_block_uuid
|
|
1542
1563
|
|
|
1543
1564
|
if self.pipeline \
|
|
@@ -2133,6 +2154,8 @@ class CallbackBlock(Block):
|
|
|
2133
2154
|
pipeline_run=pipeline_run,
|
|
2134
2155
|
),
|
|
2135
2156
|
)
|
|
2157
|
+
if parent_block:
|
|
2158
|
+
global_vars['parent_block_uuid'] = parent_block.uuid
|
|
2136
2159
|
|
|
2137
2160
|
if parent_block and \
|
|
2138
2161
|
parent_block.pipeline and \
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from enum import Enum
|
|
2
3
|
|
|
3
4
|
PIPELINES_FOLDER = 'pipelines'
|
|
@@ -19,6 +20,7 @@ DATAFRAME_ANALYSIS_MAX_COLUMNS = 100
|
|
|
19
20
|
DATAFRAME_SAMPLE_COUNT_PREVIEW = 10
|
|
20
21
|
DATAFRAME_SAMPLE_COUNT = 1000
|
|
21
22
|
DATAFRAME_SAMPLE_MAX_COLUMNS = 1000
|
|
23
|
+
MAX_PRINT_OUTPUT_LINES = int(os.getenv('MAX_PRINT_OUTPUT_LINES', 1000) or 1000)
|
|
22
24
|
VARIABLE_DIR = '.variables'
|
|
23
25
|
LOGS_DIR = '.logs'
|
|
24
26
|
|
|
@@ -82,10 +82,29 @@ def build_time_series_buckets(df, datetime_column, time_interval, metrics):
|
|
|
82
82
|
return []
|
|
83
83
|
|
|
84
84
|
datetimes = datetimes.unique()
|
|
85
|
-
min_value_datetime =
|
|
86
|
-
max_value_datetime =
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
min_value_datetime = datetimes.min()
|
|
86
|
+
max_value_datetime = datetimes.max()
|
|
87
|
+
|
|
88
|
+
if type(min_value_datetime) is str:
|
|
89
|
+
min_value_datetime = dateutil.parser.parse(min_value_datetime)
|
|
90
|
+
if type(max_value_datetime) is str:
|
|
91
|
+
max_value_datetime = dateutil.parser.parse(max_value_datetime)
|
|
92
|
+
|
|
93
|
+
# If you manually convert the datetime column to a datetime, Pandas will use numpy.datetime64
|
|
94
|
+
# type. This type does not have the methods year, month, day, etc that is used down below.
|
|
95
|
+
datetimes_temp = []
|
|
96
|
+
for dt in datetimes:
|
|
97
|
+
if type(dt) is np.datetime64:
|
|
98
|
+
datetimes_temp.append(pd.to_datetime(dt.astype(datetime)).to_pydatetime())
|
|
99
|
+
else:
|
|
100
|
+
datetimes_temp.append(dt)
|
|
101
|
+
datetimes = datetimes_temp
|
|
102
|
+
if type(min_value_datetime) is np.datetime64:
|
|
103
|
+
min_value_datetime = pd.to_datetime(min_value_datetime.astype(datetime)).to_pydatetime()
|
|
104
|
+
if type(max_value_datetime) is np.datetime64:
|
|
105
|
+
max_value_datetime = pd.to_datetime(max_value_datetime.astype(datetime)).to_pydatetime()
|
|
106
|
+
|
|
107
|
+
a, b = [dateutil.parser.parse(d) if type(d) is str else d for d in sorted(datetimes)[:2]]
|
|
89
108
|
|
|
90
109
|
year = min_value_datetime.year
|
|
91
110
|
month = min_value_datetime.month
|
|
@@ -237,6 +237,8 @@ class PipelineRun(BaseModel):
|
|
|
237
237
|
|
|
238
238
|
@property
|
|
239
239
|
def execution_partition(self) -> str:
|
|
240
|
+
if self.variables and self.variables.get('execution_partition'):
|
|
241
|
+
return self.variables.get('execution_partition')
|
|
240
242
|
if self.execution_date is None:
|
|
241
243
|
return str(self.pipeline_schedule_id)
|
|
242
244
|
else:
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
1
3
|
from mage_ai.orchestration.notification.config import AlertOn, NotificationConfig
|
|
2
4
|
from mage_ai.services.email.email import send_email
|
|
5
|
+
from mage_ai.services.google_chat.google_chat import send_google_chat_message
|
|
3
6
|
from mage_ai.services.opsgenie.opsgenie import send_opsgenie_alert
|
|
4
7
|
from mage_ai.services.slack.slack import send_slack_message
|
|
5
8
|
from mage_ai.services.teams.teams import send_teams_message
|
|
6
|
-
from mage_ai.services.google_chat.google_chat import send_google_chat_message
|
|
7
9
|
from mage_ai.settings import MAGE_PUBLIC_HOST
|
|
8
|
-
import os
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class NotificationSender:
|
|
@@ -18,10 +19,17 @@ class NotificationSender:
|
|
|
18
19
|
summary: str = None,
|
|
19
20
|
details: str = None,
|
|
20
21
|
) -> None:
|
|
22
|
+
"""Send messages to the notification channels.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
title (str, optional): Short sentence, used as title (e.g. Email subject)
|
|
26
|
+
summary (str, optional): Mid-length sentences, used as the summary of the message.
|
|
27
|
+
details (str, optional): Long message, used as the body of the message (e.g. Email body)
|
|
28
|
+
"""
|
|
21
29
|
if summary is None:
|
|
22
30
|
return
|
|
23
31
|
if self.config.slack_config is not None and self.config.slack_config.is_valid:
|
|
24
|
-
send_slack_message(self.config.slack_config, summary)
|
|
32
|
+
send_slack_message(self.config.slack_config, details or summary)
|
|
25
33
|
|
|
26
34
|
if self.config.teams_config is not None and self.config.teams_config.is_valid:
|
|
27
35
|
send_teams_message(self.config.teams_config, summary)
|
|
@@ -75,9 +83,14 @@ class NotificationSender:
|
|
|
75
83
|
f'at execution time `{pipeline_run.execution_date}`.'
|
|
76
84
|
)
|
|
77
85
|
email_content = f'{message}\n'
|
|
78
|
-
if os.getenv('ENV') != 'production':
|
|
86
|
+
if os.getenv('ENV') != 'production' or MAGE_PUBLIC_HOST != 'http://localhost:6789':
|
|
87
|
+
"""
|
|
88
|
+
Include the URL for the following cases
|
|
89
|
+
1. Dev environment: Use the default localhost as host in URL
|
|
90
|
+
2. Production environment: If MAGE_PUBLIC_HOST is set, use it as host.
|
|
91
|
+
"""
|
|
79
92
|
email_content += f'Open {self.__pipeline_run_url(pipeline, pipeline_run)} '\
|
|
80
|
-
|
|
93
|
+
'to check pipeline run results and logs.'
|
|
81
94
|
self.send(
|
|
82
95
|
title=f'Failed to run Mage pipeline {pipeline.uuid}',
|
|
83
96
|
summary=message,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import traceback
|
|
2
3
|
from datetime import datetime, timedelta
|
|
3
4
|
from typing import Any, Dict, List, Tuple
|
|
@@ -274,7 +275,9 @@ class PipelineScheduler:
|
|
|
274
275
|
tags=merge_dict(tags, dict(metrics=self.pipeline_run.metrics)),
|
|
275
276
|
)
|
|
276
277
|
|
|
277
|
-
def memory_usage_failure(self, tags: Dict =
|
|
278
|
+
def memory_usage_failure(self, tags: Dict = None) -> None:
|
|
279
|
+
if tags is None:
|
|
280
|
+
tags = dict()
|
|
278
281
|
msg = 'Memory usage across all pipeline runs has reached or exceeded the maximum '\
|
|
279
282
|
f'limit of {int(MEMORY_USAGE_MAXIMUM * 100)}%.'
|
|
280
283
|
self.logger.info(msg, tags=tags)
|
|
@@ -603,7 +606,8 @@ def run_integration_pipeline(
|
|
|
603
606
|
while True:
|
|
604
607
|
block_runs_in_order.append(
|
|
605
608
|
find(
|
|
606
|
-
lambda b: b.block_uuid ==
|
|
609
|
+
lambda b: b.block_uuid ==
|
|
610
|
+
f'{current_block.uuid}:{tap_stream_id}:{idx}', # noqa: B023
|
|
607
611
|
all_block_runs,
|
|
608
612
|
)
|
|
609
613
|
)
|
|
@@ -616,11 +620,11 @@ def run_integration_pipeline(
|
|
|
616
620
|
data_exporter_uuid = f'{data_exporter_block.uuid}:{tap_stream_id}:{idx}'
|
|
617
621
|
|
|
618
622
|
data_loader_block_run = find(
|
|
619
|
-
lambda b: b.block_uuid == data_loader_uuid,
|
|
623
|
+
lambda b: b.block_uuid == data_loader_uuid, # noqa: B023
|
|
620
624
|
all_block_runs,
|
|
621
625
|
)
|
|
622
626
|
data_exporter_block_run = find(
|
|
623
|
-
lambda b: b.block_uuid == data_exporter_uuid,
|
|
627
|
+
lambda b: b.block_uuid == data_exporter_uuid, # noqa: B023
|
|
624
628
|
block_runs_for_stream,
|
|
625
629
|
)
|
|
626
630
|
if not data_loader_block_run or not data_exporter_block_run:
|
|
@@ -653,7 +657,7 @@ def run_integration_pipeline(
|
|
|
653
657
|
block_runs_and_configs = block_runs_and_configs[1:]
|
|
654
658
|
|
|
655
659
|
block_failed = False
|
|
656
|
-
for
|
|
660
|
+
for _, tup in enumerate(block_runs_and_configs):
|
|
657
661
|
block_run, template_runtime_configuration = tup
|
|
658
662
|
|
|
659
663
|
tags_updated = merge_dict(tags, dict(
|
|
@@ -813,7 +817,7 @@ def run_block(
|
|
|
813
817
|
block_grandparent_uuid,
|
|
814
818
|
)
|
|
815
819
|
|
|
816
|
-
for idx,
|
|
820
|
+
for idx, _ in enumerate(values):
|
|
817
821
|
if idx < len(block_metadata):
|
|
818
822
|
metadata = block_metadata[idx].copy()
|
|
819
823
|
else:
|
|
@@ -880,8 +884,10 @@ def run_pipeline(pipeline_run_id, variables, tags):
|
|
|
880
884
|
def configure_pipeline_run_payload(
|
|
881
885
|
pipeline_schedule: PipelineSchedule,
|
|
882
886
|
pipeline_type: PipelineType,
|
|
883
|
-
payload: Dict =
|
|
887
|
+
payload: Dict = None,
|
|
884
888
|
) -> Tuple[Dict, bool]:
|
|
889
|
+
if payload is None:
|
|
890
|
+
payload = dict()
|
|
885
891
|
if 'variables' not in payload:
|
|
886
892
|
payload['variables'] = {}
|
|
887
893
|
|
|
@@ -893,6 +899,13 @@ def configure_pipeline_run_payload(
|
|
|
893
899
|
elif not isinstance(execution_date, datetime):
|
|
894
900
|
payload['execution_date'] = datetime.fromisoformat(execution_date)
|
|
895
901
|
|
|
902
|
+
# Set execution_partition in variables
|
|
903
|
+
payload['variables']['execution_partition'] = \
|
|
904
|
+
os.sep.join([
|
|
905
|
+
str(pipeline_schedule.id),
|
|
906
|
+
payload['execution_date'].strftime(format='%Y%m%dT%H%M%S_%f'),
|
|
907
|
+
])
|
|
908
|
+
|
|
896
909
|
is_integration = PipelineType.INTEGRATION == pipeline_type
|
|
897
910
|
if is_integration:
|
|
898
911
|
payload['create_block_runs'] = False
|
|
@@ -1115,7 +1128,9 @@ def schedule_all():
|
|
|
1115
1128
|
job_manager.clean_up_jobs()
|
|
1116
1129
|
|
|
1117
1130
|
|
|
1118
|
-
def schedule_with_event(event: Dict =
|
|
1131
|
+
def schedule_with_event(event: Dict = None):
|
|
1132
|
+
if event is None:
|
|
1133
|
+
event = dict()
|
|
1119
1134
|
logger.info(f'Schedule with event {event}')
|
|
1120
1135
|
all_event_matchers = EventMatcher.active_event_matchers()
|
|
1121
1136
|
for e in all_event_matchers:
|
|
@@ -1145,7 +1160,9 @@ def sync_schedules(pipeline_uuids: List[str]):
|
|
|
1145
1160
|
PipelineSchedule.create_or_update(pipeline_trigger)
|
|
1146
1161
|
|
|
1147
1162
|
|
|
1148
|
-
def get_variables(pipeline_run, extra_variables: Dict =
|
|
1163
|
+
def get_variables(pipeline_run, extra_variables: Dict = None) -> Dict:
|
|
1164
|
+
if extra_variables is None:
|
|
1165
|
+
extra_variables = dict()
|
|
1149
1166
|
if not pipeline_run:
|
|
1150
1167
|
return {}
|
|
1151
1168
|
|
mage_ai/server/constants.py
CHANGED