mage-ai 0.9.69__py3-none-any.whl → 0.9.70__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/BackfillPolicy.py +1 -0
- mage_ai/api/policies/PipelinePolicy.py +1 -0
- mage_ai/api/policies/WorkspacePolicy.py +1 -0
- mage_ai/api/presenters/BackfillPresenter.py +1 -0
- mage_ai/api/resources/GitBranchResource.py +56 -23
- mage_ai/api/resources/GitCustomBranchResource.py +29 -1
- mage_ai/api/resources/OauthResource.py +1 -1
- mage_ai/api/resources/PipelineResource.py +11 -5
- mage_ai/api/resources/PipelineRunResource.py +41 -4
- mage_ai/api/resources/PipelineScheduleResource.py +4 -0
- mage_ai/api/resources/PullRequestResource.py +6 -4
- mage_ai/api/resources/WorkspaceResource.py +5 -4
- mage_ai/cache/block_action_object/__init__.py +1 -1
- mage_ai/cluster_manager/kubernetes/workload_manager.py +52 -1
- mage_ai/cluster_manager/workspace/base.py +6 -0
- mage_ai/cluster_manager/workspace/kubernetes.py +22 -1
- mage_ai/command_center/applications/utils.py +2 -2
- mage_ai/command_center/presenters/text.py +1 -1
- mage_ai/data_preparation/executors/k8s_block_executor.py +30 -7
- mage_ai/data_preparation/executors/k8s_pipeline_executor.py +30 -7
- mage_ai/data_preparation/executors/streaming_pipeline_executor.py +1 -1
- mage_ai/data_preparation/git/__init__.py +50 -22
- mage_ai/data_preparation/git/api.py +62 -7
- mage_ai/data_preparation/git/utils.py +45 -21
- mage_ai/data_preparation/models/block/__init__.py +24 -5
- mage_ai/data_preparation/models/block/dynamic/utils.py +9 -4
- mage_ai/data_preparation/models/block/global_data_product/__init__.py +25 -2
- mage_ai/data_preparation/models/block/remote/__init__.py +0 -0
- mage_ai/data_preparation/models/block/remote/models.py +58 -0
- mage_ai/data_preparation/models/block/utils.py +38 -0
- mage_ai/data_preparation/models/constants.py +2 -0
- mage_ai/data_preparation/models/global_data_product/__init__.py +12 -0
- mage_ai/data_preparation/models/pipeline.py +9 -5
- mage_ai/data_preparation/models/triggers/__init__.py +4 -2
- mage_ai/data_preparation/storage/local_storage.py +12 -6
- mage_ai/orchestration/db/migrations/versions/42a14d6143f1_update_token_column_type.py +54 -0
- mage_ai/orchestration/db/models/oauth.py +10 -9
- mage_ai/orchestration/db/models/schedules.py +21 -0
- mage_ai/orchestration/notification/sender.py +37 -15
- mage_ai/orchestration/pipeline_scheduler_original.py +32 -25
- mage_ai/orchestration/triggers/api.py +29 -1
- mage_ai/orchestration/triggers/global_data_product.py +9 -4
- mage_ai/orchestration/triggers/utils.py +10 -1
- mage_ai/orchestration/utils/resources.py +3 -0
- mage_ai/server/api/downloads.py +4 -1
- mage_ai/server/api/runs.py +151 -0
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +6 -6
- mage_ai/server/frontend_dist/_next/static/{_krrrgup_C-dPOpX36S8I → RhDiJSkcjCsh4xxX4BFBk}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{3548-fa0792ddb88f4646.js → 3548-9d26185b3fb663b1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-3edc6270c5b0e962.js → frontend_dist/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-1bdfda8edc9cf4a8.js → triggers-4612d15a65c35912.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
- mage_ai/server/frontend_dist/block-layout.html +2 -2
- mage_ai/server/frontend_dist/compute.html +2 -2
- mage_ai/server/frontend_dist/files.html +2 -2
- mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
- mage_ai/server/frontend_dist/global-data-products.html +2 -2
- mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist/global-hooks.html +2 -2
- mage_ai/server/frontend_dist/index.html +2 -2
- mage_ai/server/frontend_dist/manage/files.html +2 -2
- mage_ai/server/frontend_dist/manage/settings.html +2 -2
- mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist/manage/users/new.html +2 -2
- mage_ai/server/frontend_dist/manage/users.html +2 -2
- mage_ai/server/frontend_dist/manage.html +2 -2
- mage_ai/server/frontend_dist/oauth.html +2 -2
- mage_ai/server/frontend_dist/overview.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]/dashboard.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/platform/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
- mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users/[...slug].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 +6 -6
- mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
- mage_ai/server/frontend_dist/templates.html +2 -2
- mage_ai/server/frontend_dist/terminal.html +2 -2
- mage_ai/server/frontend_dist/test.html +2 -2
- mage_ai/server/frontend_dist/triggers.html +2 -2
- mage_ai/server/frontend_dist/version-control.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/404.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/_next/static/{KLL5mirre9d7_ZeEpaw3s → TdpLLFome13qvM0gXvpHs}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{3548-fa0792ddb88f4646.js → 3548-9d26185b3fb663b1.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipeline-runs-3edc6270c5b0e962.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-1bdfda8edc9cf4a8.js → triggers-4612d15a65c35912.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist_base_path_template/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/sign-in.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
- mage_ai/server/scheduler_manager.py +3 -1
- mage_ai/server/server.py +35 -12
- mage_ai/server/utils/output_display.py +2 -2
- mage_ai/services/aws/ecs/ecs.py +1 -0
- mage_ai/services/k8s/config.py +4 -4
- mage_ai/services/k8s/utils.py +97 -0
- mage_ai/shared/parsers.py +6 -1
- mage_ai/tests/api/operations/base/mixins.py +1 -1
- mage_ai/tests/api/resources/test_pipeline_resource.py +2 -2
- mage_ai/tests/authentication/oauth/test_utils.py +1 -1
- mage_ai/tests/data_preparation/models/block/test_global_data_product.py +2 -0
- mage_ai/tests/orchestration/triggers/test_global_data_product.py +138 -136
- mage_ai/tests/server/test_server.py +19 -0
- mage_ai/tests/services/k8s/test_job_manager.py +9 -6
- mage_ai/version_control/branch/utils.py +2 -1
- mage_ai/version_control/models.py +3 -2
- {mage_ai-0.9.69.dist-info → mage_ai-0.9.70.dist-info}/METADATA +2 -2
- {mage_ai-0.9.69.dist-info → mage_ai-0.9.70.dist-info}/RECORD +217 -212
- mage_ai/server/frontend_dist/_next/static/chunks/1557-df144fbd8b2208c3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-d9c89527266296f7.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-503049734a8b082f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-5b26eeda8aed8a7b.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-8b793b3b696a2cd3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-df144fbd8b2208c3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-d9c89527266296f7.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-503049734a8b082f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-5b26eeda8aed8a7b.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-8b793b3b696a2cd3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{_krrrgup_C-dPOpX36S8I → RhDiJSkcjCsh4xxX4BFBk}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{KLL5mirre9d7_ZeEpaw3s → TdpLLFome13qvM0gXvpHs}/_ssgManifest.js +0 -0
- {mage_ai-0.9.69.dist-info → mage_ai-0.9.70.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.69.dist-info → mage_ai-0.9.70.dist-info}/WHEEL +0 -0
- {mage_ai-0.9.69.dist-info → mage_ai-0.9.70.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.69.dist-info → mage_ai-0.9.70.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, List
|
|
3
|
+
|
|
4
|
+
from mage_ai.shared.models import BaseDataClass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class RemoteBlock(BaseDataClass):
|
|
9
|
+
block_uuid: str
|
|
10
|
+
execution_partition: str = None
|
|
11
|
+
pipeline_uuid: str = None
|
|
12
|
+
repo_path: str = None
|
|
13
|
+
|
|
14
|
+
def __post_init__(self):
|
|
15
|
+
self._block = None
|
|
16
|
+
self._pipeline = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def pipeline(self):
|
|
20
|
+
from mage_ai.data_preparation.models.pipeline import Pipeline
|
|
21
|
+
|
|
22
|
+
if self._pipeline:
|
|
23
|
+
return self._pipeline
|
|
24
|
+
|
|
25
|
+
self._pipeline = Pipeline.get(
|
|
26
|
+
self.pipeline_uuid,
|
|
27
|
+
all_projects=True,
|
|
28
|
+
check_if_exists=False,
|
|
29
|
+
repo_path=self.repo_path,
|
|
30
|
+
use_repo_path=True if self.repo_path else False,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return self._pipeline
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def block(self):
|
|
37
|
+
if self._block:
|
|
38
|
+
return self._block
|
|
39
|
+
|
|
40
|
+
self._block = self.pipeline.get_block(self.block_uuid)
|
|
41
|
+
|
|
42
|
+
return self._block
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def variable_uuids(self):
|
|
46
|
+
return self.block.output_variables(execution_partition=self.execution_partition)
|
|
47
|
+
|
|
48
|
+
def get_outputs(self, **kwargs) -> List[Any]:
|
|
49
|
+
arr = []
|
|
50
|
+
for variable_uuid in self.variable_uuids:
|
|
51
|
+
output = self.pipeline.get_block_variable(
|
|
52
|
+
self.block_uuid,
|
|
53
|
+
variable_uuid,
|
|
54
|
+
partition=self.execution_partition,
|
|
55
|
+
)
|
|
56
|
+
arr.append(output)
|
|
57
|
+
|
|
58
|
+
return arr
|
|
@@ -29,6 +29,7 @@ from mage_ai.data_preparation.models.block.dynamic.utils import (
|
|
|
29
29
|
from mage_ai.data_preparation.models.block.dynamic.utils import (
|
|
30
30
|
should_reduce_output as should_reduce_output_original,
|
|
31
31
|
)
|
|
32
|
+
from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
|
|
32
33
|
from mage_ai.data_preparation.models.constants import (
|
|
33
34
|
DATAFRAME_ANALYSIS_MAX_COLUMNS,
|
|
34
35
|
BlockType,
|
|
@@ -393,6 +394,7 @@ def fetch_input_variables(
|
|
|
393
394
|
dynamic_block_flags: List[DynamicBlockFlag] = None,
|
|
394
395
|
metadata: Dict = None,
|
|
395
396
|
upstream_block_uuids_override: List[str] = None,
|
|
397
|
+
current_block=None,
|
|
396
398
|
) -> Tuple[List, List, List]:
|
|
397
399
|
"""
|
|
398
400
|
Fetches the input variables for a block.
|
|
@@ -510,6 +512,22 @@ def fetch_input_variables(
|
|
|
510
512
|
if BlockType.GLOBAL_DATA_PRODUCT == upstream_block.type:
|
|
511
513
|
global_data_product = upstream_block.get_global_data_product()
|
|
512
514
|
input_vars[idx] = global_data_product.get_outputs()
|
|
515
|
+
|
|
516
|
+
mds = {}
|
|
517
|
+
variable_uuids = upstream_block.output_variables(
|
|
518
|
+
execution_partition=execution_partition,
|
|
519
|
+
)
|
|
520
|
+
for variable_uuid in variable_uuids:
|
|
521
|
+
md = pipeline.get_block_variable(
|
|
522
|
+
upstream_block_uuid,
|
|
523
|
+
variable_uuid,
|
|
524
|
+
partition=execution_partition,
|
|
525
|
+
)
|
|
526
|
+
if isinstance(md, dict):
|
|
527
|
+
mds.update(md)
|
|
528
|
+
|
|
529
|
+
kwargs_vars.append(mds)
|
|
530
|
+
|
|
513
531
|
continue
|
|
514
532
|
|
|
515
533
|
# Block output variables for upstream_block_uuid
|
|
@@ -743,6 +761,26 @@ def fetch_input_variables(
|
|
|
743
761
|
__uuid='output_variables'
|
|
744
762
|
)
|
|
745
763
|
|
|
764
|
+
if kwargs_vars:
|
|
765
|
+
kwargs_vars2 = []
|
|
766
|
+
|
|
767
|
+
remote_blocks_output = []
|
|
768
|
+
for kwargs in kwargs_vars:
|
|
769
|
+
for remote_block_dict in kwargs.get('remote_blocks', []):
|
|
770
|
+
# Global data products only need the remote block information, not the output
|
|
771
|
+
if current_block and BlockType.GLOBAL_DATA_PRODUCT == current_block.type:
|
|
772
|
+
output = remote_block_dict
|
|
773
|
+
else:
|
|
774
|
+
output = RemoteBlock.load(**remote_block_dict).get_outputs()
|
|
775
|
+
remote_blocks_output.append(output)
|
|
776
|
+
|
|
777
|
+
for kwargs in kwargs_vars:
|
|
778
|
+
if kwargs.get('remote_blocks'):
|
|
779
|
+
kwargs['remote_blocks'] = remote_blocks_output
|
|
780
|
+
kwargs_vars2.append(kwargs)
|
|
781
|
+
|
|
782
|
+
kwargs_vars = kwargs_vars2
|
|
783
|
+
|
|
746
784
|
return input_vars, kwargs_vars, upstream_block_uuids_final
|
|
747
785
|
|
|
748
786
|
|
|
@@ -75,6 +75,18 @@ class GlobalDataProduct:
|
|
|
75
75
|
|
|
76
76
|
return self._object
|
|
77
77
|
|
|
78
|
+
def get_blocks(self) -> List:
|
|
79
|
+
arr = []
|
|
80
|
+
|
|
81
|
+
if not self.settings or len(self.settings) == 0:
|
|
82
|
+
return arr
|
|
83
|
+
|
|
84
|
+
if GlobalDataProductObjectType.PIPELINE == self.object_type:
|
|
85
|
+
for block_uuid in self.settings.keys():
|
|
86
|
+
arr.append(self.pipeline.get_block(block_uuid))
|
|
87
|
+
|
|
88
|
+
return arr
|
|
89
|
+
|
|
78
90
|
def get_outputs(self, from_notebook: bool = False, global_vars: Dict = None, **kwargs) -> Dict:
|
|
79
91
|
data = {}
|
|
80
92
|
|
|
@@ -7,7 +7,7 @@ import tempfile
|
|
|
7
7
|
import zipfile
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
from io import BytesIO
|
|
10
|
-
from typing import Any, Callable, Dict, List, Tuple, Union
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
12
|
import aiofiles
|
|
13
13
|
import pytz
|
|
@@ -99,6 +99,7 @@ class Pipeline:
|
|
|
99
99
|
self.description = description
|
|
100
100
|
self.executor_config = dict()
|
|
101
101
|
self.executor_type = None
|
|
102
|
+
self._rendered_executor_type = None # Render template variables
|
|
102
103
|
self.extensions = {}
|
|
103
104
|
self.name = None
|
|
104
105
|
self.notification_config = dict()
|
|
@@ -1768,10 +1769,13 @@ class Pipeline:
|
|
|
1768
1769
|
def get_executable_blocks(self):
|
|
1769
1770
|
return [b for b in self.blocks_by_uuid.values() if b.executable]
|
|
1770
1771
|
|
|
1771
|
-
def get_executor_type(self) -> str:
|
|
1772
|
-
if self.
|
|
1773
|
-
|
|
1774
|
-
|
|
1772
|
+
def get_executor_type(self) -> Optional[str]:
|
|
1773
|
+
if self._rendered_executor_type is None:
|
|
1774
|
+
if self.executor_type:
|
|
1775
|
+
return Template(self.executor_type).render(**get_template_vars())
|
|
1776
|
+
else:
|
|
1777
|
+
self._rendered_executor_type = self.executor_type
|
|
1778
|
+
return self._rendered_executor_type
|
|
1775
1779
|
|
|
1776
1780
|
def has_block(self, block_uuid: str, block_type: str = None, extension_uuid: str = None):
|
|
1777
1781
|
if extension_uuid:
|
|
@@ -206,6 +206,7 @@ def add_or_update_trigger_for_pipeline_and_persist(
|
|
|
206
206
|
trigger: Trigger,
|
|
207
207
|
pipeline_uuid: str,
|
|
208
208
|
update_only_if_exists: bool = False,
|
|
209
|
+
old_trigger_name: str = None,
|
|
209
210
|
) -> Dict:
|
|
210
211
|
trigger_configs_by_name = get_trigger_configs_by_name(pipeline_uuid)
|
|
211
212
|
|
|
@@ -214,13 +215,14 @@ def add_or_update_trigger_for_pipeline_and_persist(
|
|
|
214
215
|
have, so we need to set "envs" on the updated trigger if it already exists.
|
|
215
216
|
Otherwise, it will get overwritten when updating the trigger in code.
|
|
216
217
|
"""
|
|
217
|
-
|
|
218
|
+
trigger_name = trigger.name if old_trigger_name is None else old_trigger_name
|
|
219
|
+
existing_trigger = trigger_configs_by_name.get(trigger_name)
|
|
218
220
|
if existing_trigger is not None:
|
|
219
221
|
trigger.envs = existing_trigger.get('envs', [])
|
|
220
222
|
elif update_only_if_exists:
|
|
221
223
|
return None
|
|
222
224
|
|
|
223
|
-
trigger_configs_by_name[
|
|
225
|
+
trigger_configs_by_name[trigger_name] = trigger.to_dict()
|
|
224
226
|
yaml_config = dict(triggers=list(trigger_configs_by_name.values()))
|
|
225
227
|
content = yaml.safe_dump(yaml_config)
|
|
226
228
|
trigger_file_path = get_triggers_file_path(pipeline_uuid)
|
|
@@ -92,12 +92,18 @@ class LocalStorage(BaseStorage):
|
|
|
92
92
|
os.makedirs(dirname, exist_ok=True)
|
|
93
93
|
|
|
94
94
|
with open(file_path, 'w') as file:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
try:
|
|
96
|
+
simplejson.dump(
|
|
97
|
+
data,
|
|
98
|
+
file,
|
|
99
|
+
default=encode_complex,
|
|
100
|
+
ignore_nan=True,
|
|
101
|
+
)
|
|
102
|
+
except ValueError as err:
|
|
103
|
+
if is_debug():
|
|
104
|
+
raise err
|
|
105
|
+
else:
|
|
106
|
+
print(f'[ERROR] LocalStorage.write_json_file: {err}')
|
|
101
107
|
|
|
102
108
|
async def write_json_file_async(self, file_path: str, data) -> None:
|
|
103
109
|
async with aiofiles.open(file_path, mode='w') as file:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Update token column type
|
|
2
|
+
|
|
3
|
+
Revision ID: 42a14d6143f1
|
|
4
|
+
Revises: b9a2d6d0a2c7
|
|
5
|
+
Create Date: 2024-04-12 15:19:52.639580
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = '42a14d6143f1'
|
|
14
|
+
down_revision = 'b9a2d6d0a2c7'
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
21
|
+
with op.batch_alter_table('oauth2_access_token', schema=None) as batch_op:
|
|
22
|
+
batch_op.alter_column(
|
|
23
|
+
'token',
|
|
24
|
+
existing_type=sa.String(length=255),
|
|
25
|
+
type_=sa.Text(),
|
|
26
|
+
existing_nullable=True,
|
|
27
|
+
)
|
|
28
|
+
batch_op.alter_column(
|
|
29
|
+
'refresh_token',
|
|
30
|
+
existing_type=sa.String(length=255),
|
|
31
|
+
type_=sa.Text(),
|
|
32
|
+
existing_nullable=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# ### end Alembic commands ###
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def downgrade() -> None:
|
|
39
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
40
|
+
with op.batch_alter_table('oauth2_access_token', schema=None) as batch_op:
|
|
41
|
+
batch_op.alter_column(
|
|
42
|
+
'token',
|
|
43
|
+
existing_type=sa.Text(),
|
|
44
|
+
type_=sa.String(length=255),
|
|
45
|
+
existing_nullable=True,
|
|
46
|
+
)
|
|
47
|
+
batch_op.alter_column(
|
|
48
|
+
'refresh_token',
|
|
49
|
+
existing_type=sa.String(length=255),
|
|
50
|
+
type_=sa.Text(),
|
|
51
|
+
existing_nullable=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# ### end Alembic commands ###
|
|
@@ -12,6 +12,7 @@ from sqlalchemy import (
|
|
|
12
12
|
ForeignKey,
|
|
13
13
|
Integer,
|
|
14
14
|
String,
|
|
15
|
+
Text,
|
|
15
16
|
and_,
|
|
16
17
|
asc,
|
|
17
18
|
func,
|
|
@@ -324,7 +325,7 @@ class Role(BaseModel):
|
|
|
324
325
|
@classmethod
|
|
325
326
|
@safe_db_query
|
|
326
327
|
def create_default_roles(
|
|
327
|
-
|
|
328
|
+
cls,
|
|
328
329
|
entity: Entity = None,
|
|
329
330
|
entity_id: str = None,
|
|
330
331
|
name_func: Callable[[str], str] = None,
|
|
@@ -342,18 +343,18 @@ class Role(BaseModel):
|
|
|
342
343
|
entity = Entity.GLOBAL
|
|
343
344
|
permissions = Permission.create_default_permissions(entity=entity, entity_id=entity_id)
|
|
344
345
|
mapping = {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
cls.DefaultRole.OWNER: Permission.Access.OWNER,
|
|
347
|
+
cls.DefaultRole.ADMIN: Permission.Access.ADMIN,
|
|
348
|
+
cls.DefaultRole.EDITOR: Permission.Access.EDITOR,
|
|
349
|
+
cls.DefaultRole.VIEWER: Permission.Access.VIEWER,
|
|
349
350
|
}
|
|
350
351
|
for name, access in mapping.items():
|
|
351
352
|
role_name = name
|
|
352
353
|
if name_func is not None:
|
|
353
354
|
role_name = name_func(name)
|
|
354
|
-
role =
|
|
355
|
+
role = cls.query.filter(Role.name == role_name).first()
|
|
355
356
|
if not role:
|
|
356
|
-
|
|
357
|
+
cls.create(
|
|
357
358
|
name=role_name,
|
|
358
359
|
permissions=[
|
|
359
360
|
Permission.query.filter(
|
|
@@ -880,10 +881,10 @@ class Oauth2AccessToken(BaseModel):
|
|
|
880
881
|
expires = Column(DateTime(timezone=True))
|
|
881
882
|
oauth2_application = relationship(Oauth2Application, back_populates='oauth2_access_tokens')
|
|
882
883
|
oauth2_application_id = Column(Integer, ForeignKey('oauth2_application.id'))
|
|
883
|
-
token = Column(
|
|
884
|
+
token = Column(Text, index=True, unique=True)
|
|
884
885
|
user = relationship(User, back_populates='oauth2_access_tokens')
|
|
885
886
|
user_id = Column(Integer, ForeignKey('user.id'))
|
|
886
|
-
refresh_token = Column(
|
|
887
|
+
refresh_token = Column(Text)
|
|
887
888
|
|
|
888
889
|
def is_valid(self) -> bool:
|
|
889
890
|
return self.token and \
|
|
@@ -1913,3 +1913,24 @@ class Backfill(BaseModel):
|
|
|
1913
1913
|
Backfill.pipeline_schedule_id.in_(pipeline_schedule_ids),
|
|
1914
1914
|
)
|
|
1915
1915
|
return []
|
|
1916
|
+
|
|
1917
|
+
@property
|
|
1918
|
+
def pipeline_run_status_counts(self) -> Dict:
|
|
1919
|
+
status_counts = dict()
|
|
1920
|
+
execution_dates_counted = set()
|
|
1921
|
+
|
|
1922
|
+
# Sort the pipeline runs by id in reverse order so the first pipeline run
|
|
1923
|
+
# checked is the latest pipeline run created for a given execution date.
|
|
1924
|
+
pipeline_runs_sorted = sorted(
|
|
1925
|
+
self.pipeline_runs,
|
|
1926
|
+
key=lambda pr: (pr.execution_date, pr.id),
|
|
1927
|
+
reverse=True,
|
|
1928
|
+
)
|
|
1929
|
+
|
|
1930
|
+
for pr in pipeline_runs_sorted:
|
|
1931
|
+
# Only count a pipeline run once per execution date
|
|
1932
|
+
if pr.execution_date not in execution_dates_counted:
|
|
1933
|
+
status_counts[pr.status] = (status_counts.get(pr.status) or 0) + 1
|
|
1934
|
+
execution_dates_counted.add(pr.execution_date)
|
|
1935
|
+
|
|
1936
|
+
return status_counts
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import traceback
|
|
2
3
|
from typing import Dict
|
|
3
4
|
|
|
4
5
|
from mage_ai.orchestration.notification.config import (
|
|
@@ -61,33 +62,54 @@ class NotificationSender:
|
|
|
61
62
|
if summary is None:
|
|
62
63
|
return
|
|
63
64
|
if self.config.slack_config is not None and self.config.slack_config.is_valid:
|
|
64
|
-
|
|
65
|
+
try:
|
|
66
|
+
send_slack_message(self.config.slack_config, details or summary, title)
|
|
67
|
+
except Exception:
|
|
68
|
+
traceback.print_exc()
|
|
65
69
|
|
|
66
70
|
if self.config.teams_config is not None and self.config.teams_config.is_valid:
|
|
67
|
-
|
|
71
|
+
try:
|
|
72
|
+
send_teams_message(self.config.teams_config, summary)
|
|
73
|
+
except Exception:
|
|
74
|
+
traceback.print_exc()
|
|
68
75
|
|
|
69
76
|
if self.config.discord_config is not None and self.config.discord_config.is_valid:
|
|
70
|
-
|
|
77
|
+
try:
|
|
78
|
+
send_discord_message(self.config.discord_config, summary, title)
|
|
79
|
+
except Exception:
|
|
80
|
+
traceback.print_exc()
|
|
71
81
|
|
|
72
82
|
if self.config.telegram_config is not None and self.config.telegram_config.is_valid:
|
|
73
|
-
|
|
83
|
+
try:
|
|
84
|
+
send_telegram_message(self.config.telegram_config, summary, title)
|
|
85
|
+
except Exception:
|
|
86
|
+
traceback.print_exc()
|
|
74
87
|
|
|
75
88
|
if self.config.google_chat_config is not None and self.config.google_chat_config.is_valid:
|
|
76
|
-
|
|
89
|
+
try:
|
|
90
|
+
send_google_chat_message(self.config.google_chat_config, summary)
|
|
91
|
+
except Exception:
|
|
92
|
+
traceback.print_exc()
|
|
77
93
|
|
|
78
94
|
if self.config.email_config is not None and title is not None:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
try:
|
|
96
|
+
send_email(
|
|
97
|
+
self.config.email_config,
|
|
98
|
+
subject=title,
|
|
99
|
+
message=details or summary,
|
|
100
|
+
)
|
|
101
|
+
except Exception:
|
|
102
|
+
traceback.print_exc()
|
|
84
103
|
|
|
85
104
|
if self.config.opsgenie_config is not None and self.config.opsgenie_config.is_valid:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
try:
|
|
106
|
+
send_opsgenie_alert(
|
|
107
|
+
self.config.opsgenie_config,
|
|
108
|
+
message=title,
|
|
109
|
+
description=details or summary,
|
|
110
|
+
)
|
|
111
|
+
except Exception:
|
|
112
|
+
traceback.print_exc()
|
|
91
113
|
|
|
92
114
|
def send_pipeline_run_success_message(self, pipeline, pipeline_run) -> None:
|
|
93
115
|
if AlertOn.PIPELINE_RUN_SUCCESS in self.config.alert_on:
|
|
@@ -284,8 +284,7 @@ class PipelineScheduler:
|
|
|
284
284
|
or PipelineRun.PipelineRunStatus.FAILED
|
|
285
285
|
)
|
|
286
286
|
self.pipeline_run.update(status=status)
|
|
287
|
-
|
|
288
|
-
self.on_pipeline_run_failure('Pipeline run timed out.')
|
|
287
|
+
self.on_pipeline_run_failure('Pipeline run timed out.', status=status)
|
|
289
288
|
elif self.pipeline_run.any_blocks_failed() and not self.allow_blocks_to_fail:
|
|
290
289
|
self.pipeline_run.update(
|
|
291
290
|
status=PipelineRun.PipelineRunStatus.FAILED)
|
|
@@ -318,30 +317,38 @@ class PipelineScheduler:
|
|
|
318
317
|
self.__schedule_blocks(block_runs)
|
|
319
318
|
|
|
320
319
|
@safe_db_query
|
|
321
|
-
def on_pipeline_run_failure(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
message = br.metrics.get('error', {}).get('message')
|
|
327
|
-
if message:
|
|
328
|
-
message_split = message.split('\n')
|
|
329
|
-
# Truncate the error message if it has too many lines, set max
|
|
330
|
-
# lines at 50
|
|
331
|
-
if len(message_split) > 50:
|
|
332
|
-
message_split = message_split[-50:]
|
|
333
|
-
message_split.insert(0, '... (error truncated)')
|
|
334
|
-
message = '\n'.join(message_split)
|
|
335
|
-
stacktrace = f'Error for block {br.block_uuid}:\n{message}'
|
|
336
|
-
break
|
|
337
|
-
|
|
320
|
+
def on_pipeline_run_failure(
|
|
321
|
+
self,
|
|
322
|
+
error_msg: str,
|
|
323
|
+
status=PipelineRun.PipelineRunStatus.FAILED,
|
|
324
|
+
) -> None:
|
|
338
325
|
asyncio.run(UsageStatisticLogger().pipeline_run_ended(self.pipeline_run))
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
stacktrace=
|
|
344
|
-
|
|
326
|
+
|
|
327
|
+
if status == PipelineRun.PipelineRunStatus.FAILED:
|
|
328
|
+
# Only send notification when pipeline run status is FAILED
|
|
329
|
+
failed_block_runs = self.pipeline_run.failed_block_runs
|
|
330
|
+
stacktrace = None
|
|
331
|
+
for br in failed_block_runs:
|
|
332
|
+
if br.metrics:
|
|
333
|
+
message = br.metrics.get('error', {}).get('message')
|
|
334
|
+
if message:
|
|
335
|
+
message_split = message.split('\n')
|
|
336
|
+
# Truncate the error message if it has too many lines, set max
|
|
337
|
+
# lines at 50
|
|
338
|
+
if len(message_split) > 50:
|
|
339
|
+
message_split = message_split[-50:]
|
|
340
|
+
message_split.insert(0, '... (error truncated)')
|
|
341
|
+
message = '\n'.join(message_split)
|
|
342
|
+
stacktrace = f'Error for block {br.block_uuid}:\n{message}'
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
self.notification_sender.send_pipeline_run_failure_message(
|
|
346
|
+
pipeline=self.pipeline,
|
|
347
|
+
pipeline_run=self.pipeline_run,
|
|
348
|
+
error=error_msg,
|
|
349
|
+
stacktrace=stacktrace,
|
|
350
|
+
)
|
|
351
|
+
|
|
345
352
|
# Cancel block runs that are still in progress for the pipeline run.
|
|
346
353
|
cancel_block_runs_and_jobs(self.pipeline_run, self.pipeline)
|
|
347
354
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Dict, Optional
|
|
2
|
+
from typing import Dict, List, Optional, Union
|
|
3
3
|
|
|
4
4
|
from mage_ai.api.resources.PipelineScheduleResource import PipelineScheduleResource
|
|
5
|
+
from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
|
|
5
6
|
from mage_ai.data_preparation.models.pipeline import Pipeline
|
|
6
7
|
from mage_ai.data_preparation.models.triggers import ScheduleStatus, ScheduleType
|
|
7
8
|
from mage_ai.orchestration.db.models.schedules import PipelineRun, PipelineSchedule
|
|
@@ -25,11 +26,21 @@ def trigger_pipeline(
|
|
|
25
26
|
poll_timeout: Optional[float] = None,
|
|
26
27
|
schedule_name: str = None,
|
|
27
28
|
verbose: bool = True,
|
|
29
|
+
remote_blocks: List[Union[Dict, RemoteBlock]] = None,
|
|
30
|
+
return_remote_blocks: bool = False,
|
|
28
31
|
_should_schedule: bool = False, # For internal use only (e.g. running hooks from notebook).
|
|
29
32
|
) -> PipelineRun:
|
|
30
33
|
if variables is None:
|
|
31
34
|
variables = {}
|
|
32
35
|
|
|
36
|
+
if remote_blocks:
|
|
37
|
+
arr = []
|
|
38
|
+
for remote_block in remote_blocks:
|
|
39
|
+
if isinstance(remote_block, dict):
|
|
40
|
+
remote_block = RemoteBlock.load(**remote_block)
|
|
41
|
+
arr.append(remote_block.to_dict())
|
|
42
|
+
variables['remote_blocks'] = arr
|
|
43
|
+
|
|
33
44
|
pipeline = Pipeline.get(pipeline_uuid, all_projects=project_platform_activated())
|
|
34
45
|
|
|
35
46
|
pipeline_schedule = __fetch_or_create_pipeline_schedule(pipeline, schedule_name=schedule_name)
|
|
@@ -50,6 +61,23 @@ def trigger_pipeline(
|
|
|
50
61
|
verbose=verbose,
|
|
51
62
|
)
|
|
52
63
|
|
|
64
|
+
if return_remote_blocks and pipeline_run and pipeline_run.pipeline:
|
|
65
|
+
pipeline = pipeline_run.pipeline
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
dict(
|
|
69
|
+
remote_blocks=[
|
|
70
|
+
RemoteBlock.load(
|
|
71
|
+
block_uuid=block.uuid,
|
|
72
|
+
execution_partition=pipeline_run.execution_partition,
|
|
73
|
+
pipeline_uuid=pipeline.uuid,
|
|
74
|
+
repo_path=pipeline.repo_path,
|
|
75
|
+
)
|
|
76
|
+
for block in pipeline.blocks_by_uuid.values()
|
|
77
|
+
],
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
|
|
53
81
|
return pipeline_run
|
|
54
82
|
|
|
55
83
|
|
|
@@ -3,9 +3,10 @@ import json
|
|
|
3
3
|
from datetime import datetime, timedelta, timezone
|
|
4
4
|
from logging import Logger
|
|
5
5
|
from time import sleep
|
|
6
|
-
from typing import Dict, List, Optional
|
|
6
|
+
from typing import Dict, List, Optional, Union
|
|
7
7
|
|
|
8
8
|
from mage_ai.data_preparation.logging.logger import DictLogger
|
|
9
|
+
from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
|
|
9
10
|
from mage_ai.data_preparation.models.global_data_product import GlobalDataProduct
|
|
10
11
|
from mage_ai.data_preparation.models.triggers import ScheduleStatus, ScheduleType
|
|
11
12
|
from mage_ai.orchestration.db import safe_db_query
|
|
@@ -145,6 +146,7 @@ def trigger_and_check_status(
|
|
|
145
146
|
round_number: int = 0,
|
|
146
147
|
verbose: bool = True,
|
|
147
148
|
should_schedule: bool = False,
|
|
149
|
+
remote_blocks: List[Union[Dict, RemoteBlock]] = None,
|
|
148
150
|
):
|
|
149
151
|
tags = merge_dict(logging_tags, dict(
|
|
150
152
|
block_uuid=block.uuid if block else None,
|
|
@@ -163,6 +165,7 @@ def trigger_and_check_status(
|
|
|
163
165
|
print(log_message)
|
|
164
166
|
print(json.dumps(tags, indent=2))
|
|
165
167
|
|
|
168
|
+
pipeline_run = None
|
|
166
169
|
pipeline_run_created = None
|
|
167
170
|
tries = 0
|
|
168
171
|
|
|
@@ -305,6 +308,7 @@ def trigger_and_check_status(
|
|
|
305
308
|
global_data_product.pipeline,
|
|
306
309
|
pipeline_schedule,
|
|
307
310
|
dict(variables=variables),
|
|
311
|
+
remote_blocks=remote_blocks,
|
|
308
312
|
should_schedule=should_schedule,
|
|
309
313
|
)
|
|
310
314
|
if pipeline_run_created:
|
|
@@ -312,11 +316,9 @@ def trigger_and_check_status(
|
|
|
312
316
|
f'Created pipeline run {pipeline_run_created.id} for '
|
|
313
317
|
f'global data product {global_data_product.uuid}.'
|
|
314
318
|
)
|
|
319
|
+
pipeline_run = pipeline_run_created
|
|
315
320
|
|
|
316
321
|
lock.release_lock(__lock_key_for_creating_pipeline_run(global_data_product))
|
|
317
|
-
|
|
318
|
-
if pipeline_run_created:
|
|
319
|
-
break
|
|
320
322
|
else:
|
|
321
323
|
__log(
|
|
322
324
|
f'No pipeline run for global data product {global_data_product.uuid} '
|
|
@@ -339,6 +341,7 @@ def trigger_and_check_status(
|
|
|
339
341
|
poll_timeout=poll_timeout,
|
|
340
342
|
verbose=verbose,
|
|
341
343
|
should_schedule=should_schedule,
|
|
344
|
+
remote_blocks=remote_blocks,
|
|
342
345
|
)
|
|
343
346
|
break
|
|
344
347
|
else:
|
|
@@ -359,6 +362,8 @@ def trigger_and_check_status(
|
|
|
359
362
|
else:
|
|
360
363
|
break
|
|
361
364
|
|
|
365
|
+
return pipeline_run or pipeline_run_created
|
|
366
|
+
|
|
362
367
|
|
|
363
368
|
def fetch_or_create_pipeline_schedule(global_data_product: GlobalDataProduct) -> PipelineSchedule:
|
|
364
369
|
pipeline_uuid = global_data_product.object_uuid
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from datetime import datetime, timedelta
|
|
2
2
|
from time import sleep
|
|
3
|
-
from typing import Dict, Optional
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
4
|
|
|
5
|
+
from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
|
|
5
6
|
from mage_ai.data_preparation.models.pipeline import Pipeline
|
|
6
7
|
from mage_ai.data_preparation.models.triggers import ScheduleStatus
|
|
7
8
|
from mage_ai.orchestration.db import db_connection, safe_db_query
|
|
@@ -84,6 +85,7 @@ def create_and_start_pipeline_run(
|
|
|
84
85
|
pipeline_schedule: PipelineSchedule,
|
|
85
86
|
payload: Dict = None,
|
|
86
87
|
should_schedule: bool = False,
|
|
88
|
+
remote_blocks: List[Union[Dict, RemoteBlock]] = None,
|
|
87
89
|
) -> PipelineRun:
|
|
88
90
|
if payload is None:
|
|
89
91
|
payload = {}
|
|
@@ -94,6 +96,13 @@ def create_and_start_pipeline_run(
|
|
|
94
96
|
payload,
|
|
95
97
|
)
|
|
96
98
|
|
|
99
|
+
if remote_blocks:
|
|
100
|
+
variables = configured_payload.get('variables', {})
|
|
101
|
+
if variables.get('remote_blocks'):
|
|
102
|
+
remote_blocks = variables.get('remote_blocks', []) + remote_blocks
|
|
103
|
+
variables['remote_blocks'] = remote_blocks
|
|
104
|
+
configured_payload['variables'] = variables
|
|
105
|
+
|
|
97
106
|
pipeline_run = PipelineRun.create(**configured_payload)
|
|
98
107
|
|
|
99
108
|
# Do not start the pipeline run immediately due to concurrency control
|