mage-ai 0.9.67__py3-none-any.whl → 0.9.68__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/monitors/BaseMonitor.py +1 -2
- mage_ai/api/policies/PipelinePolicy.py +2 -0
- mage_ai/api/resources/BlockLayoutItemResource.py +2 -2
- mage_ai/api/resources/BlockResource.py +4 -3
- mage_ai/api/resources/CacheItemResource.py +1 -1
- mage_ai/api/resources/GitBranchResource.py +3 -5
- mage_ai/api/resources/PageBlockLayoutResource.py +2 -2
- mage_ai/api/resources/PipelineResource.py +13 -0
- mage_ai/api/resources/PipelineRunResource.py +10 -1
- mage_ai/cache/tag.py +3 -0
- mage_ai/cluster_manager/manage.py +4 -1
- mage_ai/data_preparation/executors/k8s_block_executor.py +8 -1
- mage_ai/data_preparation/executors/k8s_pipeline_executor.py +12 -1
- mage_ai/data_preparation/logging/gcs_logger_manager.py +7 -4
- mage_ai/data_preparation/models/block/__init__.py +21 -68
- mage_ai/data_preparation/models/block/block_factory.py +77 -0
- mage_ai/data_preparation/models/block/dbt/block.py +5 -7
- mage_ai/data_preparation/models/block/global_data_product/__init__.py +9 -3
- mage_ai/data_preparation/models/block/integration/__init__.py +12 -8
- mage_ai/data_preparation/models/block/platform/mixins.py +1 -1
- mage_ai/data_preparation/models/pipeline.py +80 -13
- mage_ai/data_preparation/storage/gcs_storage.py +1 -1
- mage_ai/data_preparation/templates/constants.py +7 -0
- mage_ai/io/export_utils.py +3 -0
- mage_ai/io/oracledb.py +138 -3
- mage_ai/io/sql.py +4 -0
- mage_ai/orchestration/db/__init__.py +2 -2
- mage_ai/orchestration/db/models/schedules.py +2 -2
- mage_ai/orchestration/notification/sender.py +8 -0
- mage_ai/orchestration/pipeline_scheduler_original.py +10 -1
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/_next/static/chunks/3548-961ff79ca70038c7.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{4241-ccd0126f5750cc35.js → 4241-4499461184ba0d23.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/5627-237a3de578538022.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{7966-5c1786fb7e7a48f5.js → 7966-f07b2913f7326b50.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-08790743315de36a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-13760bb72d823b69.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-90abafc7ed61c582.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/khKiaJtwrslgMmp4YSa1f → frontend_dist/_next/static/i8pymuJDTVHdWjUP1QSh1}/_buildManifest.js +1 -1
- 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 +3 -3
- 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 +5 -5
- 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 +2 -2
- mage_ai/server/{frontend_dist/_next/static/vPsMu6Fi2zrHaf2fRXKRO → frontend_dist_base_path_template/_next/static/CKCvjsYCf2imD2X8zAOBf}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-961ff79ca70038c7.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{4241-ccd0126f5750cc35.js → 4241-4499461184ba0d23.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5627-237a3de578538022.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{7966-5c1786fb7e7a48f5.js → 7966-f07b2913f7326b50.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-08790743315de36a.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-13760bb72d823b69.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-90abafc7ed61c582.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.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 +3 -3
- 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 +5 -5
- 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/services/k8s/job_manager.py +8 -0
- mage_ai/services/slack/slack.py +10 -1
- mage_ai/services/spark/spark.py +9 -2
- mage_ai/settings/backends.py +8 -8
- mage_ai/settings/models/configuration_option.py +10 -9
- mage_ai/shared/files.py +19 -1
- mage_ai/shared/path_fixer.py +3 -0
- mage_ai/streaming/sources/base.py +5 -0
- mage_ai/streaming/sources/kafka.py +2 -1
- mage_ai/streaming/sources/mongodb.py +4 -0
- mage_ai/tests/data_preparation/models/block/dbt/test_block.py +2 -2
- mage_ai/tests/data_preparation/models/block/dbt/test_block_sql.py +1 -1
- mage_ai/tests/data_preparation/models/block/dbt/test_block_yaml.py +1 -1
- mage_ai/tests/data_preparation/models/test_block.py +2 -1
- mage_ai/tests/orchestration/test_pipeline_scheduler.py +2 -0
- mage_ai/tests/settings/models/test_configuration_option.py +2 -2
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/METADATA +3 -3
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/RECORD +207 -206
- mage_ai/server/frontend_dist/_next/static/chunks/181-e61915415a976861.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3548-13563a1ff815f922.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-dde29a463495cebb.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/181-e61915415a976861.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-13563a1ff815f922.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-dde29a463495cebb.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{vPsMu6Fi2zrHaf2fRXKRO → i8pymuJDTVHdWjUP1QSh1}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{khKiaJtwrslgMmp4YSa1f → CKCvjsYCf2imD2X8zAOBf}/_ssgManifest.js +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/WHEEL +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/top_level.txt +0 -0
|
@@ -416,9 +416,10 @@ class SourceBlock(IntegrationBlock):
|
|
|
416
416
|
class DestinationBlock(IntegrationBlock):
|
|
417
417
|
def to_dict(
|
|
418
418
|
self,
|
|
419
|
-
include_content=False,
|
|
420
|
-
include_outputs=False,
|
|
421
|
-
|
|
419
|
+
include_content: bool = False,
|
|
420
|
+
include_outputs: bool = False,
|
|
421
|
+
include_block_pipelines: bool = False,
|
|
422
|
+
sample_count: int = None,
|
|
422
423
|
check_if_file_exists: bool = False,
|
|
423
424
|
destination_table: str = None,
|
|
424
425
|
state_stream: str = None,
|
|
@@ -444,6 +445,7 @@ class DestinationBlock(IntegrationBlock):
|
|
|
444
445
|
super().to_dict(
|
|
445
446
|
include_content=include_content,
|
|
446
447
|
include_outputs=include_outputs,
|
|
448
|
+
include_block_pipelines=include_block_pipelines,
|
|
447
449
|
sample_count=sample_count,
|
|
448
450
|
check_if_file_exists=check_if_file_exists,
|
|
449
451
|
),
|
|
@@ -452,9 +454,10 @@ class DestinationBlock(IntegrationBlock):
|
|
|
452
454
|
|
|
453
455
|
async def to_dict_async(
|
|
454
456
|
self,
|
|
455
|
-
include_content=False,
|
|
456
|
-
include_outputs=False,
|
|
457
|
-
|
|
457
|
+
include_content: bool = False,
|
|
458
|
+
include_outputs: bool = False,
|
|
459
|
+
include_block_pipelines: bool = False,
|
|
460
|
+
sample_count: int = None,
|
|
458
461
|
check_if_file_exists: bool = False,
|
|
459
462
|
destination_table: str = None,
|
|
460
463
|
state_stream: str = None,
|
|
@@ -463,13 +466,14 @@ class DestinationBlock(IntegrationBlock):
|
|
|
463
466
|
return self.to_dict(
|
|
464
467
|
include_content=include_content,
|
|
465
468
|
include_outputs=include_outputs,
|
|
469
|
+
include_block_pipelines=include_block_pipelines,
|
|
466
470
|
sample_count=sample_count,
|
|
467
471
|
check_if_file_exists=check_if_file_exists,
|
|
468
472
|
destination_table=destination_table,
|
|
469
473
|
state_stream=state_stream,
|
|
470
474
|
)
|
|
471
475
|
|
|
472
|
-
def update(self, data, update_state=False):
|
|
476
|
+
def update(self, data, update_state=False, **kwargs):
|
|
473
477
|
if update_state:
|
|
474
478
|
from mage_ai.data_preparation.models.pipelines.integration_pipeline import (
|
|
475
479
|
IntegrationPipeline,
|
|
@@ -493,7 +497,7 @@ class DestinationBlock(IntegrationBlock):
|
|
|
493
497
|
bookmark_values=bookmark_values
|
|
494
498
|
)
|
|
495
499
|
|
|
496
|
-
return super().update(data)
|
|
500
|
+
return super().update(data, **kwargs)
|
|
497
501
|
|
|
498
502
|
def output_variables(self, execution_partition: str = None) -> List[str]:
|
|
499
503
|
return []
|
|
@@ -15,8 +15,10 @@ import yaml
|
|
|
15
15
|
from jinja2 import Template
|
|
16
16
|
|
|
17
17
|
from mage_ai.authentication.permissions.constants import EntityName
|
|
18
|
+
from mage_ai.cache.block import BlockCache
|
|
18
19
|
from mage_ai.cache.pipeline import PipelineCache
|
|
19
20
|
from mage_ai.data_preparation.models.block import Block, run_blocks, run_blocks_sync
|
|
21
|
+
from mage_ai.data_preparation.models.block.block_factory import BlockFactory
|
|
20
22
|
from mage_ai.data_preparation.models.block.data_integration.utils import (
|
|
21
23
|
convert_outputs_to_data,
|
|
22
24
|
)
|
|
@@ -82,6 +84,8 @@ class Pipeline:
|
|
|
82
84
|
repo_config=None,
|
|
83
85
|
catalog=None,
|
|
84
86
|
use_repo_path: bool = False,
|
|
87
|
+
description: str = None,
|
|
88
|
+
tags: List[str] = None,
|
|
85
89
|
):
|
|
86
90
|
self.block_configs = []
|
|
87
91
|
self.blocks_by_uuid = {}
|
|
@@ -90,7 +94,7 @@ class Pipeline:
|
|
|
90
94
|
self.concurrency_config = dict()
|
|
91
95
|
self.created_at = None
|
|
92
96
|
self.data_integration = None
|
|
93
|
-
self.description =
|
|
97
|
+
self.description = description
|
|
94
98
|
self.executor_config = dict()
|
|
95
99
|
self.executor_type = None
|
|
96
100
|
self.extensions = {}
|
|
@@ -101,7 +105,7 @@ class Pipeline:
|
|
|
101
105
|
self.run_pipeline_in_one_process = False
|
|
102
106
|
self.schedules = []
|
|
103
107
|
self.settings = {}
|
|
104
|
-
self.tags = []
|
|
108
|
+
self.tags = tags or []
|
|
105
109
|
self.type = PipelineType.PYTHON
|
|
106
110
|
self.use_repo_path = use_repo_path
|
|
107
111
|
self.uuid = uuid
|
|
@@ -215,7 +219,14 @@ class Pipeline:
|
|
|
215
219
|
self.widget_configs
|
|
216
220
|
|
|
217
221
|
@classmethod
|
|
218
|
-
def create(
|
|
222
|
+
def create(
|
|
223
|
+
self,
|
|
224
|
+
name: str,
|
|
225
|
+
description: str = None,
|
|
226
|
+
pipeline_type: PipelineType = PipelineType.PYTHON,
|
|
227
|
+
repo_path: str = None,
|
|
228
|
+
tags: List[str] = None,
|
|
229
|
+
):
|
|
219
230
|
"""
|
|
220
231
|
1. Create a new folder for pipeline
|
|
221
232
|
2. Create a new yaml file to store pipeline config
|
|
@@ -231,14 +242,20 @@ class Pipeline:
|
|
|
231
242
|
with open(os.path.join(pipeline_path, PIPELINE_CONFIG_FILE), 'w') as fp:
|
|
232
243
|
yaml.dump(dict(
|
|
233
244
|
created_at=str(datetime.now(tz=pytz.UTC)),
|
|
245
|
+
description=description,
|
|
234
246
|
name=name,
|
|
247
|
+
tags=tags or [],
|
|
235
248
|
uuid=uuid,
|
|
236
249
|
type=format_enum(pipeline_type or PipelineType.PYTHON),
|
|
237
250
|
), fp)
|
|
251
|
+
|
|
238
252
|
pipeline = Pipeline(
|
|
239
253
|
uuid,
|
|
254
|
+
description=description,
|
|
240
255
|
repo_path=repo_path,
|
|
256
|
+
tags=tags or [],
|
|
241
257
|
)
|
|
258
|
+
|
|
242
259
|
return pipeline
|
|
243
260
|
|
|
244
261
|
@classmethod
|
|
@@ -368,6 +385,54 @@ class Pipeline:
|
|
|
368
385
|
IntegrationPipeline,
|
|
369
386
|
)
|
|
370
387
|
|
|
388
|
+
config_path, repo_path = self._get_config_path(
|
|
389
|
+
uuid,
|
|
390
|
+
repo_path=repo_path,
|
|
391
|
+
all_projects=all_projects,
|
|
392
|
+
use_repo_path=use_repo_path,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if check_if_exists and not os.path.exists(config_path):
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
pipeline = self(uuid, repo_path=repo_path, use_repo_path=use_repo_path)
|
|
399
|
+
if PipelineType.INTEGRATION == pipeline.type:
|
|
400
|
+
pipeline = IntegrationPipeline(uuid, repo_path=repo_path)
|
|
401
|
+
|
|
402
|
+
return pipeline
|
|
403
|
+
|
|
404
|
+
@classmethod
|
|
405
|
+
def get_config(
|
|
406
|
+
self,
|
|
407
|
+
uuid,
|
|
408
|
+
repo_path: str = None,
|
|
409
|
+
all_projects: bool = False,
|
|
410
|
+
use_repo_path: bool = False,
|
|
411
|
+
):
|
|
412
|
+
config_path, _ = self._get_config_path(
|
|
413
|
+
uuid,
|
|
414
|
+
repo_path=repo_path,
|
|
415
|
+
all_projects=all_projects,
|
|
416
|
+
use_repo_path=use_repo_path,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
metadata_path = os.path.join(config_path, PIPELINE_CONFIG_FILE)
|
|
420
|
+
|
|
421
|
+
if not os.path.exists(metadata_path):
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
with open(metadata_path) as fp:
|
|
425
|
+
config = yaml.full_load(fp) or {}
|
|
426
|
+
return config
|
|
427
|
+
|
|
428
|
+
@classmethod
|
|
429
|
+
def _get_config_path(
|
|
430
|
+
self,
|
|
431
|
+
uuid,
|
|
432
|
+
repo_path: str = None,
|
|
433
|
+
all_projects: bool = False,
|
|
434
|
+
use_repo_path: bool = False,
|
|
435
|
+
) -> Tuple[str, str]:
|
|
371
436
|
if all_projects and not use_repo_path and project_platform_activated():
|
|
372
437
|
from mage_ai.settings.platform.utils import get_pipeline_config_path
|
|
373
438
|
|
|
@@ -379,15 +444,7 @@ class Pipeline:
|
|
|
379
444
|
PIPELINES_FOLDER,
|
|
380
445
|
uuid,
|
|
381
446
|
)
|
|
382
|
-
|
|
383
|
-
if check_if_exists and not os.path.exists(config_path):
|
|
384
|
-
return None
|
|
385
|
-
|
|
386
|
-
pipeline = self(uuid, repo_path=repo_path, use_repo_path=use_repo_path)
|
|
387
|
-
if PipelineType.INTEGRATION == pipeline.type:
|
|
388
|
-
pipeline = IntegrationPipeline(uuid, repo_path=repo_path)
|
|
389
|
-
|
|
390
|
-
return pipeline
|
|
447
|
+
return config_path, repo_path
|
|
391
448
|
|
|
392
449
|
@classmethod
|
|
393
450
|
async def load_metadata(
|
|
@@ -717,7 +774,8 @@ class Pipeline:
|
|
|
717
774
|
)
|
|
718
775
|
|
|
719
776
|
language = c.get('language')
|
|
720
|
-
|
|
777
|
+
|
|
778
|
+
return BlockFactory.block_class_from_type(block_type, language=language, pipeline=self)(
|
|
721
779
|
c.get('name'),
|
|
722
780
|
c.get('uuid'),
|
|
723
781
|
block_type,
|
|
@@ -931,6 +989,8 @@ class Pipeline:
|
|
|
931
989
|
include_outputs=include_outputs,
|
|
932
990
|
sample_count=sample_count,
|
|
933
991
|
)
|
|
992
|
+
if include_block_pipelines:
|
|
993
|
+
shared_kwargs['block_cache'] = BlockCache()
|
|
934
994
|
blocks_data = await asyncio.gather(
|
|
935
995
|
*[b.to_dict_async(**merge_dict(shared_kwargs, dict(
|
|
936
996
|
include_block_catalog=include_block_catalog,
|
|
@@ -991,6 +1051,7 @@ class Pipeline:
|
|
|
991
1051
|
old_uuid = None
|
|
992
1052
|
blocks_to_remove_from_cache = []
|
|
993
1053
|
block_uuids_to_add_to_cache = []
|
|
1054
|
+
tags_to_remove_from_cache = []
|
|
994
1055
|
should_update_block_cache = False
|
|
995
1056
|
should_update_tag_cache = False
|
|
996
1057
|
|
|
@@ -1043,6 +1104,10 @@ class Pipeline:
|
|
|
1043
1104
|
old_tags = self.tags or []
|
|
1044
1105
|
|
|
1045
1106
|
if sorted(new_tags) != sorted(old_tags):
|
|
1107
|
+
tags_diff = set(old_tags).symmetric_difference(set(new_tags))
|
|
1108
|
+
for tag in tags_diff:
|
|
1109
|
+
if tag in old_tags:
|
|
1110
|
+
tags_to_remove_from_cache.append(tag)
|
|
1046
1111
|
self.tags = new_tags
|
|
1047
1112
|
should_save = True
|
|
1048
1113
|
should_update_tag_cache = True
|
|
@@ -1296,6 +1361,8 @@ class Pipeline:
|
|
|
1296
1361
|
|
|
1297
1362
|
cache = await TagCache.initialize_cache()
|
|
1298
1363
|
|
|
1364
|
+
for tag_uuid in tags_to_remove_from_cache:
|
|
1365
|
+
cache.remove_pipeline(tag_uuid, self.uuid, self.repo_path)
|
|
1299
1366
|
for tag_uuid in self.tags:
|
|
1300
1367
|
if old_uuid:
|
|
1301
1368
|
cache.remove_pipeline(tag_uuid, old_uuid, self.repo_path)
|
|
@@ -148,7 +148,7 @@ class GCSStorage(BaseStorage):
|
|
|
148
148
|
df.write_parquet(buffer)
|
|
149
149
|
buffer.seek(0)
|
|
150
150
|
blob = self.bucket.blob(gcs_url_path(file_path))
|
|
151
|
-
blob.upload_from_string(buffer)
|
|
151
|
+
blob.upload_from_string(buffer.getvalue())
|
|
152
152
|
|
|
153
153
|
@contextmanager
|
|
154
154
|
def open_to_write(self, file_path: str) -> None:
|
|
@@ -608,6 +608,13 @@ TEMPLATES_ONLY_FOR_V2 = [
|
|
|
608
608
|
name='MySQL',
|
|
609
609
|
path='data_exporters/mysql.py',
|
|
610
610
|
),
|
|
611
|
+
dict(
|
|
612
|
+
block_type=BlockType.DATA_EXPORTER,
|
|
613
|
+
groups=[GROUP_DATABASES],
|
|
614
|
+
language=BlockLanguage.PYTHON,
|
|
615
|
+
name='OracleDB',
|
|
616
|
+
path='data_exporters/oracledb.py',
|
|
617
|
+
),
|
|
611
618
|
dict(
|
|
612
619
|
block_type=BlockType.DATA_EXPORTER,
|
|
613
620
|
groups=[GROUP_DATABASES],
|
mage_ai/io/export_utils.py
CHANGED
|
@@ -108,6 +108,7 @@ def gen_table_creation_query(
|
|
|
108
108
|
case_sensitive: bool = False,
|
|
109
109
|
unique_constraints: List[str] = None,
|
|
110
110
|
overwrite_types: Dict = None,
|
|
111
|
+
skip_semicolon_at_end: bool = False,
|
|
111
112
|
) -> str:
|
|
112
113
|
"""
|
|
113
114
|
Generates a database table creation query from a data frame.
|
|
@@ -157,4 +158,6 @@ def gen_table_creation_query(
|
|
|
157
158
|
query.append(
|
|
158
159
|
f"CONSTRAINT {index_name} UNIQUE ({', '.join(unique_constraints_escaped)})",
|
|
159
160
|
)
|
|
161
|
+
if skip_semicolon_at_end:
|
|
162
|
+
return f'CREATE TABLE {full_table_name} (' + ','.join(query) + ')'
|
|
160
163
|
return f'CREATE TABLE {full_table_name} (' + ','.join(query) + ');'
|
mage_ai/io/oracledb.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from typing import Union
|
|
2
|
+
from typing import IO, Any, List, Union
|
|
3
3
|
|
|
4
|
+
import numpy as np
|
|
4
5
|
import oracledb
|
|
5
|
-
|
|
6
|
+
import simplejson
|
|
7
|
+
from pandas import DataFrame, Series, read_sql
|
|
6
8
|
|
|
7
|
-
from mage_ai.io.base import QUERY_ROW_LIMIT
|
|
9
|
+
from mage_ai.io.base import QUERY_ROW_LIMIT, ExportWritePolicy
|
|
8
10
|
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
11
|
+
from mage_ai.io.export_utils import PandasTypes
|
|
9
12
|
from mage_ai.io.sql import BaseSQL
|
|
10
13
|
from mage_ai.server.logger import Logger
|
|
14
|
+
from mage_ai.shared.parsers import encode_complex
|
|
11
15
|
|
|
12
16
|
logger = Logger().new_server_logger(__name__)
|
|
13
17
|
|
|
@@ -122,3 +126,134 @@ SELECT *
|
|
|
122
126
|
FROM subquery
|
|
123
127
|
FETCH FIRST {limit} ROWS ONLY
|
|
124
128
|
"""
|
|
129
|
+
|
|
130
|
+
def table_exists(self, schema_name: str, table_name: str) -> bool:
|
|
131
|
+
with self.conn.cursor() as cur:
|
|
132
|
+
try:
|
|
133
|
+
cur.execute(f"select * from {table_name} where rownum=1")
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
logger.info(f"Table not existing: {table_name}. Exception: {exc}")
|
|
136
|
+
return False
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
def get_type(self, column: Series, dtype: str) -> str:
|
|
140
|
+
if dtype in (
|
|
141
|
+
PandasTypes.MIXED,
|
|
142
|
+
PandasTypes.UNKNOWN_ARRAY,
|
|
143
|
+
PandasTypes.COMPLEX,
|
|
144
|
+
):
|
|
145
|
+
return 'CHAR(255)'
|
|
146
|
+
elif dtype in (PandasTypes.DATETIME, PandasTypes.DATETIME64):
|
|
147
|
+
try:
|
|
148
|
+
if column.dt.tz:
|
|
149
|
+
return 'TIMESTAMP'
|
|
150
|
+
except AttributeError:
|
|
151
|
+
pass
|
|
152
|
+
return 'TIMESTAMP'
|
|
153
|
+
elif dtype == PandasTypes.TIME:
|
|
154
|
+
try:
|
|
155
|
+
if column.dt.tz:
|
|
156
|
+
return 'TIMESTAMP'
|
|
157
|
+
except AttributeError:
|
|
158
|
+
pass
|
|
159
|
+
return 'TIMESTAMP'
|
|
160
|
+
elif dtype == PandasTypes.DATE:
|
|
161
|
+
return 'DATE'
|
|
162
|
+
elif dtype == PandasTypes.STRING:
|
|
163
|
+
return 'CHAR(255)'
|
|
164
|
+
elif dtype == PandasTypes.CATEGORICAL:
|
|
165
|
+
return 'CHAR(255)'
|
|
166
|
+
elif dtype == PandasTypes.BYTES:
|
|
167
|
+
return 'CHAR(255)'
|
|
168
|
+
elif dtype in (PandasTypes.FLOATING, PandasTypes.DECIMAL, PandasTypes.MIXED_INTEGER_FLOAT):
|
|
169
|
+
return 'NUMBER'
|
|
170
|
+
elif dtype == PandasTypes.INTEGER:
|
|
171
|
+
max_int, min_int = column.max(), column.min()
|
|
172
|
+
if np.int16(max_int) == max_int and np.int16(min_int) == min_int:
|
|
173
|
+
return 'NUMBER'
|
|
174
|
+
elif np.int32(max_int) == max_int and np.int32(min_int) == min_int:
|
|
175
|
+
return 'NUMBER'
|
|
176
|
+
else:
|
|
177
|
+
return 'NUMBER'
|
|
178
|
+
elif dtype == PandasTypes.BOOLEAN:
|
|
179
|
+
return 'CHAR(52)'
|
|
180
|
+
elif dtype in (PandasTypes.TIMEDELTA, PandasTypes.TIMEDELTA64, PandasTypes.PERIOD):
|
|
181
|
+
return 'NUMBER'
|
|
182
|
+
elif dtype == PandasTypes.EMPTY:
|
|
183
|
+
return 'CHAR(255)'
|
|
184
|
+
else:
|
|
185
|
+
print(f'Invalid datatype provided: {dtype}')
|
|
186
|
+
|
|
187
|
+
return 'CHAR(255)'
|
|
188
|
+
|
|
189
|
+
def export(
|
|
190
|
+
self,
|
|
191
|
+
df: DataFrame,
|
|
192
|
+
table_name: str = None,
|
|
193
|
+
if_exists: ExportWritePolicy = ExportWritePolicy.REPLACE,
|
|
194
|
+
**kwargs,
|
|
195
|
+
) -> None:
|
|
196
|
+
super().export(
|
|
197
|
+
df,
|
|
198
|
+
**kwargs,
|
|
199
|
+
table_name=table_name,
|
|
200
|
+
if_exists=if_exists,
|
|
201
|
+
# Oracle cursor execute will automatically add a semicolon at the end of the query.
|
|
202
|
+
skip_semicolon_at_end=True
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def upload_dataframe(
|
|
206
|
+
self,
|
|
207
|
+
cursor: Any,
|
|
208
|
+
df: DataFrame,
|
|
209
|
+
db_dtypes: List[str],
|
|
210
|
+
dtypes: List[str],
|
|
211
|
+
full_table_name: str,
|
|
212
|
+
buffer: Union[IO, None] = None,
|
|
213
|
+
**kwargs,
|
|
214
|
+
) -> None:
|
|
215
|
+
def serialize_obj(val):
|
|
216
|
+
if type(val) is dict or type(val) is np.ndarray:
|
|
217
|
+
return simplejson.dumps(
|
|
218
|
+
val,
|
|
219
|
+
default=encode_complex,
|
|
220
|
+
ignore_nan=True,
|
|
221
|
+
)
|
|
222
|
+
elif type(val) is list and len(val) >= 1 and type(val[0]) is dict:
|
|
223
|
+
return simplejson.dumps(
|
|
224
|
+
val,
|
|
225
|
+
default=encode_complex,
|
|
226
|
+
ignore_nan=True,
|
|
227
|
+
)
|
|
228
|
+
return val
|
|
229
|
+
|
|
230
|
+
# Create values
|
|
231
|
+
df_ = df.copy()
|
|
232
|
+
columns = df_.columns
|
|
233
|
+
for col in columns:
|
|
234
|
+
dtype = df_[col].dtype
|
|
235
|
+
if dtype == PandasTypes.OBJECT:
|
|
236
|
+
df_[col] = df_[col].apply(lambda x: serialize_obj(x))
|
|
237
|
+
elif dtype in (
|
|
238
|
+
PandasTypes.MIXED,
|
|
239
|
+
PandasTypes.UNKNOWN_ARRAY,
|
|
240
|
+
PandasTypes.COMPLEX,
|
|
241
|
+
):
|
|
242
|
+
df_[col] = df_[col].astype('string')
|
|
243
|
+
|
|
244
|
+
# Remove extraneous surrounding double quotes
|
|
245
|
+
# that get added while performing conversion to string.
|
|
246
|
+
df_[col] = df_[col].apply(lambda x: x.strip('"') if x and isinstance(x, str) else x)
|
|
247
|
+
df_.replace({np.NaN: None}, inplace=True)
|
|
248
|
+
values = []
|
|
249
|
+
for i in range(0, len(df_)):
|
|
250
|
+
values.append(tuple(df_.fillna('').values[i]))
|
|
251
|
+
|
|
252
|
+
# Create values placeholder
|
|
253
|
+
colmn_names = df.columns.tolist()
|
|
254
|
+
values_placeholder = ""
|
|
255
|
+
for i in range(0, len(colmn_names)):
|
|
256
|
+
values_placeholder += f':{str(i + 1)},'
|
|
257
|
+
|
|
258
|
+
insert_sql = f'INSERT INTO {full_table_name} VALUES({values_placeholder.rstrip(",")})'
|
|
259
|
+
cursor.executemany(insert_sql, values)
|
mage_ai/io/sql.py
CHANGED
|
@@ -56,6 +56,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
56
56
|
case_sensitive: bool = False,
|
|
57
57
|
unique_constraints: List[str] = None,
|
|
58
58
|
overwrite_types: Dict = None,
|
|
59
|
+
skip_semicolon_at_end: bool = False,
|
|
59
60
|
**kwargs,
|
|
60
61
|
) -> str:
|
|
61
62
|
if unique_constraints is None:
|
|
@@ -68,6 +69,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
68
69
|
case_sensitive=case_sensitive,
|
|
69
70
|
unique_constraints=unique_constraints,
|
|
70
71
|
overwrite_types=overwrite_types,
|
|
72
|
+
skip_semicolon_at_end=skip_semicolon_at_end,
|
|
71
73
|
)
|
|
72
74
|
|
|
73
75
|
def build_create_table_as_command(
|
|
@@ -234,6 +236,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
234
236
|
query_string: Union[str, None] = None,
|
|
235
237
|
unique_conflict_method: str = None,
|
|
236
238
|
unique_constraints: List[str] = None,
|
|
239
|
+
skip_semicolon_at_end: bool = False,
|
|
237
240
|
**kwargs,
|
|
238
241
|
) -> None:
|
|
239
242
|
"""
|
|
@@ -350,6 +353,7 @@ class BaseSQL(BaseSQLConnection):
|
|
|
350
353
|
case_sensitive=case_sensitive,
|
|
351
354
|
unique_constraints=unique_constraints,
|
|
352
355
|
overwrite_types=overwrite_types,
|
|
356
|
+
skip_semicolon_at_end=skip_semicolon_at_end,
|
|
353
357
|
)
|
|
354
358
|
cur.execute(query)
|
|
355
359
|
self.upload_dataframe(
|
|
@@ -13,7 +13,7 @@ from mage_ai.orchestration.db.setup import get_postgres_connection_url
|
|
|
13
13
|
from mage_ai.orchestration.db.utils import get_user_info_from_db_connection_url
|
|
14
14
|
from mage_ai.settings import OTEL_EXPORTER_OTLP_ENDPOINT
|
|
15
15
|
from mage_ai.settings.repo import get_variables_dir
|
|
16
|
-
from mage_ai.shared.environments import
|
|
16
|
+
from mage_ai.shared.environments import is_debug, is_test
|
|
17
17
|
|
|
18
18
|
DB_RETRY_COUNT = 2
|
|
19
19
|
TEST_DB = 'test.db'
|
|
@@ -170,5 +170,5 @@ def safe_db_query(func):
|
|
|
170
170
|
|
|
171
171
|
logging.basicConfig()
|
|
172
172
|
|
|
173
|
-
if
|
|
173
|
+
if is_debug() and not os.getenv('DISABLE_DATABASE_TERMINAL_OUTPUT'):
|
|
174
174
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
|
@@ -934,9 +934,9 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
|
|
|
934
934
|
|
|
935
935
|
@property
|
|
936
936
|
def pipeline_tags(self):
|
|
937
|
-
|
|
937
|
+
pipeline_config = Pipeline.get_config(self.pipeline_uuid)
|
|
938
938
|
|
|
939
|
-
return
|
|
939
|
+
return pipeline_config.get('tags') if pipeline_config is not None else []
|
|
940
940
|
|
|
941
941
|
def executable_block_runs(
|
|
942
942
|
self,
|
|
@@ -108,6 +108,7 @@ class NotificationSender:
|
|
|
108
108
|
pipeline,
|
|
109
109
|
pipeline_run,
|
|
110
110
|
error: str = None,
|
|
111
|
+
stacktrace: str = None,
|
|
111
112
|
summary: str = None,
|
|
112
113
|
) -> None:
|
|
113
114
|
if AlertOn.PIPELINE_RUN_FAILURE in self.config.alert_on:
|
|
@@ -122,6 +123,7 @@ class NotificationSender:
|
|
|
122
123
|
pipeline_run,
|
|
123
124
|
error=error,
|
|
124
125
|
message_template=message_template,
|
|
126
|
+
stacktrace=stacktrace,
|
|
125
127
|
summary=summary,
|
|
126
128
|
)
|
|
127
129
|
|
|
@@ -145,6 +147,7 @@ class NotificationSender:
|
|
|
145
147
|
pipeline,
|
|
146
148
|
pipeline_run,
|
|
147
149
|
error: str = None,
|
|
150
|
+
stacktrace: str = None,
|
|
148
151
|
):
|
|
149
152
|
if text is None or pipeline is None or pipeline_run is None:
|
|
150
153
|
return text
|
|
@@ -155,6 +158,7 @@ class NotificationSender:
|
|
|
155
158
|
pipeline_schedule_id=pipeline_run.pipeline_schedule.id,
|
|
156
159
|
pipeline_schedule_name=pipeline_run.pipeline_schedule.name,
|
|
157
160
|
pipeline_uuid=pipeline.uuid,
|
|
161
|
+
stacktrace=stacktrace,
|
|
158
162
|
)
|
|
159
163
|
|
|
160
164
|
def __send_pipeline_run_message(
|
|
@@ -164,6 +168,7 @@ class NotificationSender:
|
|
|
164
168
|
pipeline_run,
|
|
165
169
|
error: str = None,
|
|
166
170
|
message_template: MessageTemplate = None,
|
|
171
|
+
stacktrace: str = None,
|
|
167
172
|
summary: str = None,
|
|
168
173
|
):
|
|
169
174
|
"""Shared method to send pipeline run message of multiple types (success, failure, etc.).
|
|
@@ -207,18 +212,21 @@ class NotificationSender:
|
|
|
207
212
|
pipeline,
|
|
208
213
|
pipeline_run,
|
|
209
214
|
error=error,
|
|
215
|
+
stacktrace=stacktrace,
|
|
210
216
|
),
|
|
211
217
|
summary=self.__interpolate_vars(
|
|
212
218
|
summary or default_summary,
|
|
213
219
|
pipeline,
|
|
214
220
|
pipeline_run,
|
|
215
221
|
error=error,
|
|
222
|
+
stacktrace=stacktrace,
|
|
216
223
|
),
|
|
217
224
|
details=self.__interpolate_vars(
|
|
218
225
|
details or default_details,
|
|
219
226
|
pipeline,
|
|
220
227
|
pipeline_run,
|
|
221
228
|
error=error,
|
|
229
|
+
stacktrace=stacktrace,
|
|
222
230
|
),
|
|
223
231
|
)
|
|
224
232
|
|
|
@@ -320,11 +320,19 @@ class PipelineScheduler:
|
|
|
320
320
|
@safe_db_query
|
|
321
321
|
def on_pipeline_run_failure(self, error_msg: str) -> None:
|
|
322
322
|
failed_block_runs = self.pipeline_run.failed_block_runs
|
|
323
|
+
stacktrace = None
|
|
323
324
|
for br in failed_block_runs:
|
|
324
325
|
if br.metrics:
|
|
325
326
|
message = br.metrics.get('error', {}).get('message')
|
|
326
327
|
if message:
|
|
327
|
-
|
|
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}'
|
|
328
336
|
break
|
|
329
337
|
|
|
330
338
|
asyncio.run(UsageStatisticLogger().pipeline_run_ended(self.pipeline_run))
|
|
@@ -332,6 +340,7 @@ class PipelineScheduler:
|
|
|
332
340
|
pipeline=self.pipeline,
|
|
333
341
|
pipeline_run=self.pipeline_run,
|
|
334
342
|
error=error_msg,
|
|
343
|
+
stacktrace=stacktrace,
|
|
335
344
|
)
|
|
336
345
|
# Cancel block runs that are still in progress for the pipeline run.
|
|
337
346
|
cancel_block_runs_and_jobs(self.pipeline_run, self.pipeline)
|
mage_ai/server/constants.py
CHANGED