mage-ai 0.9.74__py3-none-any.whl → 0.9.76__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/ai/llm_pipeline_wizard.py +6 -4
- mage_ai/ai/openai_client.py +7 -5
- mage_ai/api/policies/PipelineSchedulePolicy.py +1 -0
- mage_ai/api/presenters/PipelineSchedulePresenter.py +11 -2
- mage_ai/api/resources/PipelineScheduleResource.py +11 -3
- mage_ai/api/resources/PipelineTriggerResource.py +3 -1
- mage_ai/api/resources/SessionResource.py +2 -2
- mage_ai/api/resources/SyncResource.py +1 -1
- mage_ai/api/resources/UserResource.py +1 -1
- mage_ai/data_integrations/destinations/constants.py +3 -0
- mage_ai/data_integrations/sources/constants.py +2 -0
- mage_ai/data_preparation/models/block/__init__.py +4 -5
- mage_ai/data_preparation/models/block/dbt/dbt_adapter.py +20 -8
- mage_ai/data_preparation/models/block/dynamic/constants.py +0 -1
- mage_ai/data_preparation/models/block/dynamic/counter.py +1 -3
- mage_ai/data_preparation/models/block/r/__init__.py +16 -5
- mage_ai/data_preparation/models/block/sql/__init__.py +2 -0
- mage_ai/data_preparation/models/block/sql/mssql.py +8 -0
- mage_ai/data_preparation/models/block/sql/utils/shared.py +6 -2
- mage_ai/data_preparation/models/constants.py +2 -1
- mage_ai/data_preparation/models/pipeline.py +5 -0
- mage_ai/data_preparation/templates/constants.py +7 -0
- mage_ai/data_preparation/templates/data_exporters/streaming/elasticsearch.yaml +3 -0
- mage_ai/data_preparation/templates/data_loaders/airtable.py +28 -0
- mage_ai/data_preparation/templates/data_loaders/streaming/nats.yaml +6 -3
- mage_ai/data_preparation/templates/repo/io_config.yaml +2 -0
- mage_ai/io/airtable.py +104 -0
- mage_ai/io/base.py +30 -1
- mage_ai/io/bigquery.py +36 -0
- mage_ai/io/config.py +5 -0
- mage_ai/io/mssql.py +5 -0
- mage_ai/io/mysql.py +6 -1
- mage_ai/io/oracledb.py +2 -4
- mage_ai/io/postgres.py +38 -19
- mage_ai/io/redshift.py +13 -0
- mage_ai/io/sql.py +1 -0
- mage_ai/io/utils.py +18 -0
- mage_ai/orchestration/db/__init__.py +23 -3
- mage_ai/orchestration/db/models/schedules.py +2 -5
- mage_ai/orchestration/db/models/secrets.py +11 -1
- mage_ai/orchestration/metrics/pipeline_run.py +1 -1
- mage_ai/orchestration/pipeline_scheduler_original.py +4 -5
- mage_ai/orchestration/pipeline_scheduler_project_platform.py +4 -5
- mage_ai/orchestration/utils/distributed_lock.py +8 -1
- mage_ai/server/api/triggers.py +9 -0
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +3 -3
- mage_ai/server/frontend_dist/_next/static/chunks/{1187-839336d276186105.js → 1187-4560c3895e1d7099.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{1598-0adca9dce3ba4c60.js → 1598-cbf3f5a6078fc3f5.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/2717-638a944d24d5abde.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/3548-36f746b1824004f2.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{3763-39a5174f6a3924db.js → 3763-aabe2703076636b0.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3782-3e2acb5ed45b582b.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/449-5e2253c6aba42557.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/5627-d5e559859dd0e1e0.js → frontend_dist/_next/static/chunks/5627-10e76bafa5a26f5f.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{5699-e49718dfc9eb2854.js → 5699-e99379e332bd0b41.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{7966-163da2621b8c987c.js → 7966-a5a7db345ce81263.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-782dd4a6b13e1c42.js +2 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/compute-ed67fa8e81662e8b.js → frontend_dist/_next/static/chunks/pages/compute-9e2dea78024e3bb4.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/files-e0ecd7ced09a63b2.js → frontend_dist/_next/static/chunks/pages/files-e08c7fe76f968f9c.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/global-data-products/{[...slug]-c7a729477ecda50e.js → [...slug]-30c3807057a4e65b.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{global-data-products-fd6ae6a358a60a0c.js → global-data-products-8dcb3b31af9e0e39.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/global-hooks/[...slug]-8e50243797a7fe59.js → frontend_dist/_next/static/chunks/pages/global-hooks/[...slug]-85a64b64d27214b6.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{global-hooks-d0c003446332dc0d.js → global-hooks-4ff959d51b8a9502.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/{files-a69ed8e9f814490c.js → files-d08a460641d0efaa.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/{overview-1aad7093c6d39257.js → overview-aae747f487e08d51.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/{pipeline-runs-528d30e0d13b0cc7.js → pipeline-runs-09a842d64a6ada62.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/{settings-fb9201d9cf63031d.js → settings-2e98e57d9376a458.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/{[user]-000f5a980a07da39.js → [user]-7be6e41ad66089bb.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/new-e4e613f6e817a733.js → frontend_dist/_next/static/chunks/pages/manage/users/new-4c088833063bfa07.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-5db54821a3059c69.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/manage-34d718b8a4066c23.js → frontend_dist/_next/static/chunks/pages/manage-868fcd8cbeb265f0.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{oauth-3bfd1b8d7f036726.js → oauth-6ceceb62191dfe8a.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-f65416f6dbe30ad3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipeline-runs-5f8c100e648efa8a.js → pipeline-runs-2d0136b51b57de93.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-688c652f3296bb9c.js → [...slug]-1ad5238742e25b4c.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-bd11e87d026bfbf9.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{dashboard-1236e36d39b1637d.js → dashboard-0f4f47f721b0723f.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-432da20df91511fb.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-fe91dfb0091f6bc6.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runs-0d68d4bf6290fefb.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runs-cf794b2d22a80f31.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-9254358d58f07714.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-a964caef91bed9e1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{monitors-821001e690caebe2.js → monitors-80bebb4401eefe25.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-2eae7cb017027682.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs-2d4b2a0800a66b33.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-e3d7fdfeb7f806f7.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-03d9bca3bc5e6088.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-707ed8ca942ca802.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/{[...slug]-259143ed3cf59e31.js → [...slug]-8429f17d4146e1ec.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-193045d9836d8d80.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-d25d07db166cbb04.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/platform/global-hooks/{[...slug]-5eeec927e4202b63.js → [...slug]-6834ae87bd668cb2.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/platform/global-hooks-fbe9ad995d46d837.js → frontend_dist/_next/static/chunks/pages/platform/global-hooks-b3f7309a23e592b2.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/{profile-fc659962d4015cb3.js → profile-f8b7374385e1f1bf.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8de68502a9afa299.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-a4f88c334414402b.js → frontend_dist/_next/static/chunks/pages/settings/platform/settings-50fb6a34f3913f1f.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/permissions/{[...slug]-4deb9579ef99a3c6.js → [...slug]-2e5c098c21ea32b7.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{permissions-e0cda2f2bfce8d61.js → permissions-54e4b15b9585bfc4.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-040f83d75d0f6537.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/roles/{[...slug]-910257d16c604ebd.js → [...slug]-95088f43034e3c95.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{roles-4f7a0756806cee34.js → roles-e9149e1fcf218f42.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{sync-data-208d6f955204d704.js → sync-data-75b67ae4a00818ef.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users/{[...slug]-c89dc67e5a1706a8.js → [...slug]-557dda05ca6c6124.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-fa61dc6c1370e6a5.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{sign-in-054b33312d3193c3.js → sign-in-593c40985d63fcf7.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/templates/{[...slug]-b6ed6a5d818bfd20.js → [...slug]-252c4b6b818345d5.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{templates-852357bc983af2ea.js → templates-ca528bc607753ab8.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/terminal-1f9c56d671bbc67d.js → frontend_dist/_next/static/chunks/pages/terminal-287362c1defcc96b.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/test-2f83af8c9f1378fe.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-d9de73fb799efed8.js +1 -0
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/version-control-ae3469b992a341d6.js → frontend_dist/_next/static/chunks/pages/version-control-573f0225d7a703ed.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/qR0jauUABqPaFMjUsYeoG/_buildManifest.js +1 -0
- mage_ai/server/frontend_dist/block-layout.html +3 -3
- mage_ai/server/frontend_dist/compute.html +6 -6
- mage_ai/server/frontend_dist/files.html +6 -6
- mage_ai/server/frontend_dist/global-data-products/[...slug].html +6 -6
- mage_ai/server/frontend_dist/global-data-products.html +6 -6
- mage_ai/server/frontend_dist/global-hooks/[...slug].html +6 -6
- mage_ai/server/frontend_dist/global-hooks.html +6 -6
- mage_ai/server/frontend_dist/index.html +3 -3
- mage_ai/server/frontend_dist/manage/files.html +6 -6
- mage_ai/server/frontend_dist/manage/overview.html +6 -6
- mage_ai/server/frontend_dist/manage/pipeline-runs.html +6 -6
- mage_ai/server/frontend_dist/manage/settings.html +6 -6
- mage_ai/server/frontend_dist/manage/users/[user].html +6 -6
- mage_ai/server/frontend_dist/manage/users/new.html +6 -6
- mage_ai/server/frontend_dist/manage/users.html +6 -6
- mage_ai/server/frontend_dist/manage.html +6 -6
- mage_ai/server/frontend_dist/oauth.html +5 -5
- mage_ai/server/frontend_dist/overview.html +6 -6
- mage_ai/server/frontend_dist/pipeline-runs.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +3 -3
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +6 -6
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +3 -3
- mage_ai/server/frontend_dist/pipelines.html +6 -6
- mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +6 -6
- mage_ai/server/frontend_dist/platform/global-hooks.html +6 -6
- mage_ai/server/frontend_dist/settings/account/profile.html +6 -6
- mage_ai/server/frontend_dist/settings/platform/preferences.html +6 -6
- mage_ai/server/frontend_dist/settings/platform/settings.html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/permissions.html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/roles.html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +6 -6
- mage_ai/server/frontend_dist/settings/workspace/users.html +6 -6
- mage_ai/server/frontend_dist/settings.html +3 -3
- mage_ai/server/frontend_dist/sign-in.html +7 -7
- mage_ai/server/frontend_dist/templates/[...slug].html +6 -6
- mage_ai/server/frontend_dist/templates.html +6 -6
- mage_ai/server/frontend_dist/terminal.html +6 -6
- mage_ai/server/frontend_dist/test.html +3 -3
- mage_ai/server/frontend_dist/triggers.html +6 -6
- mage_ai/server/frontend_dist/v2/canvas.html +2 -2
- mage_ai/server/frontend_dist/v2.html +2 -2
- mage_ai/server/frontend_dist/version-control.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/404.html +3 -3
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1187-839336d276186105.js → 1187-4560c3895e1d7099.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1598-0adca9dce3ba4c60.js → 1598-cbf3f5a6078fc3f5.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-638a944d24d5abde.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-36f746b1824004f2.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{3763-39a5174f6a3924db.js → 3763-aabe2703076636b0.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3782-3e2acb5ed45b582b.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/449-5e2253c6aba42557.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/5627-d5e559859dd0e1e0.js → frontend_dist_base_path_template/_next/static/chunks/5627-10e76bafa5a26f5f.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{5699-e49718dfc9eb2854.js → 5699-e99379e332bd0b41.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{7966-163da2621b8c987c.js → 7966-a5a7db345ce81263.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-ee5e328aaf51c698.js +2 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/compute-ed67fa8e81662e8b.js → frontend_dist_base_path_template/_next/static/chunks/pages/compute-9e2dea78024e3bb4.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/files-e0ecd7ced09a63b2.js → frontend_dist_base_path_template/_next/static/chunks/pages/files-e08c7fe76f968f9c.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-data-products/{[...slug]-c7a729477ecda50e.js → [...slug]-30c3807057a4e65b.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{global-data-products-fd6ae6a358a60a0c.js → global-data-products-8dcb3b31af9e0e39.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/global-hooks/[...slug]-8e50243797a7fe59.js → frontend_dist_base_path_template/_next/static/chunks/pages/global-hooks/[...slug]-85a64b64d27214b6.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{global-hooks-d0c003446332dc0d.js → global-hooks-4ff959d51b8a9502.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/{files-a69ed8e9f814490c.js → files-d08a460641d0efaa.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/{overview-1aad7093c6d39257.js → overview-aae747f487e08d51.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/{pipeline-runs-528d30e0d13b0cc7.js → pipeline-runs-09a842d64a6ada62.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/{settings-fb9201d9cf63031d.js → settings-2e98e57d9376a458.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/{[user]-000f5a980a07da39.js → [user]-7be6e41ad66089bb.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/manage/users/new-e4e613f6e817a733.js → frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/new-4c088833063bfa07.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users-5db54821a3059c69.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/manage-34d718b8a4066c23.js → frontend_dist_base_path_template/_next/static/chunks/pages/manage-868fcd8cbeb265f0.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{oauth-3bfd1b8d7f036726.js → oauth-6ceceb62191dfe8a.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-f65416f6dbe30ad3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{pipeline-runs-5f8c100e648efa8a.js → pipeline-runs-2d0136b51b57de93.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-688c652f3296bb9c.js → [...slug]-1ad5238742e25b4c.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-bd11e87d026bfbf9.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{dashboard-1236e36d39b1637d.js → dashboard-0f4f47f721b0723f.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-432da20df91511fb.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-fe91dfb0091f6bc6.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runs-0d68d4bf6290fefb.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runs-cf794b2d22a80f31.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-9254358d58f07714.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/block-runtime-a964caef91bed9e1.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{monitors-821001e690caebe2.js → monitors-80bebb4401eefe25.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-2eae7cb017027682.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-2d4b2a0800a66b33.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs-e3d7fdfeb7f806f7.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-03d9bca3bc5e6088.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-707ed8ca942ca802.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/{[...slug]-259143ed3cf59e31.js → [...slug]-8429f17d4146e1ec.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-193045d9836d8d80.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-d25d07db166cbb04.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/platform/global-hooks/{[...slug]-5eeec927e4202b63.js → [...slug]-6834ae87bd668cb2.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/platform/global-hooks-fbe9ad995d46d837.js → frontend_dist_base_path_template/_next/static/chunks/pages/platform/global-hooks-b3f7309a23e592b2.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/account/{profile-fc659962d4015cb3.js → profile-f8b7374385e1f1bf.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8de68502a9afa299.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/settings/platform/settings-a4f88c334414402b.js → frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-50fb6a34f3913f1f.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/permissions/{[...slug]-4deb9579ef99a3c6.js → [...slug]-2e5c098c21ea32b7.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{permissions-e0cda2f2bfce8d61.js → permissions-54e4b15b9585bfc4.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-040f83d75d0f6537.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/roles/{[...slug]-910257d16c604ebd.js → [...slug]-95088f43034e3c95.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{roles-4f7a0756806cee34.js → roles-e9149e1fcf218f42.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{sync-data-208d6f955204d704.js → sync-data-75b67ae4a00818ef.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users/{[...slug]-c89dc67e5a1706a8.js → [...slug]-557dda05ca6c6124.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users-fa61dc6c1370e6a5.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{sign-in-054b33312d3193c3.js → sign-in-593c40985d63fcf7.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/templates/{[...slug]-b6ed6a5d818bfd20.js → [...slug]-252c4b6b818345d5.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{templates-852357bc983af2ea.js → templates-ca528bc607753ab8.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/terminal-1f9c56d671bbc67d.js → frontend_dist_base_path_template/_next/static/chunks/pages/terminal-287362c1defcc96b.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/test-2f83af8c9f1378fe.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-d9de73fb799efed8.js +1 -0
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/version-control-ae3469b992a341d6.js → frontend_dist_base_path_template/_next/static/chunks/pages/version-control-573f0225d7a703ed.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{webpack-12ad70eb5c31aa92.js → webpack-5f4be622608d9267.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/iCySon3_GCldnbC5U7C-s/_buildManifest.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/block-layout.html +3 -3
- mage_ai/server/frontend_dist_base_path_template/compute.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/files.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/global-data-products.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/global-hooks.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/index.html +3 -3
- mage_ai/server/frontend_dist_base_path_template/manage/files.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage/overview.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage/pipeline-runs.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage/settings.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage/users.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/manage.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/oauth.html +5 -5
- mage_ai/server/frontend_dist_base_path_template/overview.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +3 -3
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +3 -3
- mage_ai/server/frontend_dist_base_path_template/pipelines.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/settings.html +3 -3
- mage_ai/server/frontend_dist_base_path_template/sign-in.html +7 -7
- mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +6 -6
- mage_ai/server/frontend_dist_base_path_template/templates.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/terminal.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/test.html +3 -3
- mage_ai/server/frontend_dist_base_path_template/triggers.html +6 -6
- mage_ai/server/frontend_dist_base_path_template/v2/canvas.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/v2.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/version-control.html +6 -6
- mage_ai/server/server.py +9 -0
- mage_ai/services/teams/config.py +13 -2
- mage_ai/services/teams/teams.py +13 -11
- mage_ai/settings/server.py +2 -0
- mage_ai/shared/constants.py +3 -1
- mage_ai/shared/enum.py +2 -5
- mage_ai/shared/environments.py +27 -3
- mage_ai/streaming/sinks/elasticsearch.py +15 -5
- mage_ai/tests/api/endpoints/test_blocks.py +1 -101
- mage_ai/tests/api/endpoints/test_configuration_options.py +1 -48
- mage_ai/tests/api/endpoints/test_configuration_options_project_platform.py +68 -0
- mage_ai/tests/api/endpoints/test_custom_designs.py +1 -106
- mage_ai/tests/api/endpoints/test_custom_designs_project_platform.py +132 -0
- mage_ai/tests/api/endpoints/test_dbt_blocks.py +111 -0
- mage_ai/tests/api/endpoints/test_file_contents.py +0 -48
- mage_ai/tests/api/endpoints/test_file_contents_with_project_platform.py +66 -0
- mage_ai/tests/api/endpoints/test_pipelines.py +0 -134
- mage_ai/tests/api/endpoints/test_pipelines_with_project_platform.py +143 -0
- mage_ai/tests/data_preparation/models/block/dynamic/test_counter.py +1 -3
- mage_ai/tests/data_preparation/models/block/hook/test_hook_block.py +3 -3
- mage_ai/tests/data_preparation/models/test_block.py +7 -0
- mage_ai/tests/data_preparation/test_repo_manager.py +0 -63
- mage_ai/tests/data_preparation/test_repo_manager_project_platform.py +67 -0
- mage_ai/tests/data_preparation/test_variable_manager.py +0 -51
- mage_ai/tests/data_preparation/test_variable_manager_project_platform.py +41 -0
- mage_ai/tests/io/create_table/test_postgresql.py +27 -0
- mage_ai/tests/orchestration/notification/test_config.py +3 -3
- mage_ai/usage_statistics/logger.py +99 -15
- mage_ai-0.9.76.dist-info/METADATA +393 -0
- {mage_ai-0.9.74.dist-info → mage_ai-0.9.76.dist-info}/RECORD +331 -322
- {mage_ai-0.9.74.dist-info → mage_ai-0.9.76.dist-info}/WHEEL +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/1557-1ad0c64c2d08e569.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/2717-d65056b6b5e124eb.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3548-b2c5edfb710886a6.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/3782-4b3091e550f809a2.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-5bdff745074fb350.js +0 -2
- mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-a5e9d77ed5b50205.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-69af3253ad0d0d89.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-fe112809feb25b05.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-e9ca358209cdf93d.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-a29f1615d2e7d330.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-6d382ae5bad9745c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-cd49372ae1702963.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-135be8974f7f5f2b.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-3af13e89adff4d6c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-b578b075a8d857e3.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-058d283ee178c038.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-7b02bb70462144cb.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-5212c01a9dc558da.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/test-86b12cc12d4a625f.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-2481c40b18d5b6d4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/pLWT6Sqd09xYpufCVIqnz/_buildManifest.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/JQewSAObpbhO0wrdAM6Ng/_buildManifest.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-1ad0c64c2d08e569.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-d65056b6b5e124eb.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-b2c5edfb710886a6.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3782-4b3091e550f809a2.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-90de19bc03f1484b.js +0 -2
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users-a5e9d77ed5b50205.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-69af3253ad0d0d89.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-fe112809feb25b05.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-e9ca358209cdf93d.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-a29f1615d2e7d330.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-6d382ae5bad9745c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-cd49372ae1702963.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-135be8974f7f5f2b.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-3af13e89adff4d6c.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-b578b075a8d857e3.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-058d283ee178c038.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-7b02bb70462144cb.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users-5212c01a9dc558da.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/test-86b12cc12d4a625f.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-2481c40b18d5b6d4.js +0 -1
- mage_ai-0.9.74.dist-info/METADATA +0 -544
- /mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-5bdff745074fb350.js.LICENSE.txt → _app-782dd4a6b13e1c42.js.LICENSE.txt} +0 -0
- /mage_ai/server/frontend_dist/_next/static/{pLWT6Sqd09xYpufCVIqnz → qR0jauUABqPaFMjUsYeoG}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{_app-90de19bc03f1484b.js.LICENSE.txt → _app-ee5e328aaf51c698.js.LICENSE.txt} +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{JQewSAObpbhO0wrdAM6Ng → iCySon3_GCldnbC5U7C-s}/_ssgManifest.js +0 -0
- {mage_ai-0.9.74.dist-info → mage_ai-0.9.76.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.74.dist-info → mage_ai-0.9.76.dist-info/licenses}/LICENSE +0 -0
- {mage_ai-0.9.74.dist-info → mage_ai-0.9.76.dist-info}/top_level.txt +0 -0
mage_ai/io/airtable.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import polars as pl
|
|
5
|
+
from pyairtable.api import Api
|
|
6
|
+
|
|
7
|
+
from mage_ai.io.base import BaseIO
|
|
8
|
+
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Airtable(BaseIO):
|
|
12
|
+
"""
|
|
13
|
+
Handles data transfer between Airtable tables and the Mage app.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
token: str,
|
|
19
|
+
verbose: bool = True,
|
|
20
|
+
**kwargs) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Initializes a connection to Airtable.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
token (str): Airtable API token.
|
|
26
|
+
verbose (bool, optional): Whether to print verbose output. Defaults to True.
|
|
27
|
+
**kwargs: Additional keyword arguments to pass to the pyairtable Api constructor.
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(verbose=verbose)
|
|
30
|
+
self.client = Api(token, **kwargs) # Create the Airtable API client
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def with_config(
|
|
34
|
+
cls,
|
|
35
|
+
config: BaseConfigLoader
|
|
36
|
+
) -> 'Airtable':
|
|
37
|
+
"""
|
|
38
|
+
Initializes an Airtable client from a configuration loader.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config (BaseConfigLoader): Configuration loader object.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If no valid Airtable API token is found in the configuration.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Airtable: An instance of the Airtable class.
|
|
48
|
+
"""
|
|
49
|
+
if ConfigKey.AIRTABLE_ACCESS_TOKEN not in config:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
'No valid API token found for Airtable.'
|
|
52
|
+
'You must specify your access token in your config.'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return cls(
|
|
56
|
+
token=config[ConfigKey.AIRTABLE_ACCESS_TOKEN]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def load(
|
|
60
|
+
self,
|
|
61
|
+
base_id: str,
|
|
62
|
+
table_name: str,
|
|
63
|
+
**kwargs,
|
|
64
|
+
) -> pd.DataFrame:
|
|
65
|
+
"""
|
|
66
|
+
Loads data from an Airtable table into a Pandas DataFrame.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
base_id (str): The ID of the Airtable base (e.g., 'app*****').
|
|
70
|
+
table_name (str): The name or ID of the Airtable table (e.g., 'tbl*****').
|
|
71
|
+
**kwargs: Additional keyword arguments to pass to the pyairtable table.all() method.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
DataFrame: A Pandas DataFrame containing the data from the Airtable table.
|
|
75
|
+
"""
|
|
76
|
+
with self.printer.print_msg(
|
|
77
|
+
f'Loading data frame from table \'{table_name}\' at airtable app \'{base_id}\''
|
|
78
|
+
):
|
|
79
|
+
table = self.client.table(base_id, table_name) # Get the Airtable table
|
|
80
|
+
data = table.all(**kwargs) # Fetch all records from the table
|
|
81
|
+
|
|
82
|
+
# Flatten the Airtable data structure into a list of dictionaries
|
|
83
|
+
flattened_data = []
|
|
84
|
+
for record in data:
|
|
85
|
+
flattened_record = {
|
|
86
|
+
'id': record['id'],
|
|
87
|
+
'createdTime': record['createdTime']
|
|
88
|
+
}
|
|
89
|
+
fields = record['fields']
|
|
90
|
+
flattened_record.update(fields)
|
|
91
|
+
flattened_data.append(flattened_record)
|
|
92
|
+
|
|
93
|
+
return pd.DataFrame(flattened_data) # Create and return a DataFrame
|
|
94
|
+
|
|
95
|
+
def export(
|
|
96
|
+
self,
|
|
97
|
+
df: Union[pd.DataFrame, pl.DataFrame],
|
|
98
|
+
*args,
|
|
99
|
+
**kwargs
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Not implemented yet. This method is intended to export data to Airtable.
|
|
103
|
+
"""
|
|
104
|
+
pass
|
mage_ai/io/base.py
CHANGED
|
@@ -16,6 +16,7 @@ QUERY_ROW_LIMIT = 10_000_000
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class DataSource(StrEnum):
|
|
19
|
+
AIRTABLE = 'airtable'
|
|
19
20
|
ALGOLIA = 'algolia'
|
|
20
21
|
API = 'api'
|
|
21
22
|
BIGQUERY = 'bigquery'
|
|
@@ -47,6 +48,26 @@ class FileFormat(StrEnum):
|
|
|
47
48
|
PARQUET = 'parquet'
|
|
48
49
|
HDF5 = 'hdf5'
|
|
49
50
|
XML = 'xml'
|
|
51
|
+
EXCEL = 'excel'
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_extension(cls, ext: str):
|
|
55
|
+
if ext == 'csv':
|
|
56
|
+
return cls.CSV
|
|
57
|
+
elif ext == 'json':
|
|
58
|
+
return cls.JSON
|
|
59
|
+
elif ext == 'parquet':
|
|
60
|
+
return cls.PARQUET
|
|
61
|
+
elif ext == 'hdf5':
|
|
62
|
+
return cls.HDF5
|
|
63
|
+
elif ext == 'xml':
|
|
64
|
+
return cls.XML
|
|
65
|
+
elif ext in ('xls', 'xlsx'):
|
|
66
|
+
return cls.EXCEL
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f'None file format found for this file extension: {ext}'
|
|
70
|
+
)
|
|
50
71
|
|
|
51
72
|
|
|
52
73
|
class ExportWritePolicy(BaseEnum):
|
|
@@ -126,7 +147,9 @@ class BaseFile(BaseIO):
|
|
|
126
147
|
pass
|
|
127
148
|
|
|
128
149
|
def _get_file_format(self, filepath: Union[os.PathLike, str]) -> str:
|
|
129
|
-
return
|
|
150
|
+
return FileFormat.from_extension(
|
|
151
|
+
os.path.splitext(os.path.basename(filepath))[-1][1:]
|
|
152
|
+
)
|
|
130
153
|
|
|
131
154
|
def __get_reader(self, format: Union[FileFormat, str, None]) -> Callable:
|
|
132
155
|
"""
|
|
@@ -151,6 +174,8 @@ class BaseFile(BaseIO):
|
|
|
151
174
|
return pd.read_hdf
|
|
152
175
|
elif format == FileFormat.XML:
|
|
153
176
|
return pd.read_xml
|
|
177
|
+
elif format == FileFormat.EXCEL:
|
|
178
|
+
return pd.read_excel
|
|
154
179
|
else:
|
|
155
180
|
raise ValueError(f"Invalid format '{format}' specified.")
|
|
156
181
|
|
|
@@ -249,6 +274,8 @@ class BaseFile(BaseIO):
|
|
|
249
274
|
return df.write_hdf5
|
|
250
275
|
elif format == FileFormat.XML:
|
|
251
276
|
return df.write_xml
|
|
277
|
+
elif format == FileFormat.EXCEL:
|
|
278
|
+
return df.write_excel
|
|
252
279
|
return df.write_parquet
|
|
253
280
|
|
|
254
281
|
elif isinstance(df, DataFrame): # pandas DataFrame
|
|
@@ -260,6 +287,8 @@ class BaseFile(BaseIO):
|
|
|
260
287
|
return df.to_hdf
|
|
261
288
|
elif format == FileFormat.XML:
|
|
262
289
|
return df.to_xml
|
|
290
|
+
elif format == FileFormat.EXCEL:
|
|
291
|
+
return df.to_excel
|
|
263
292
|
return df.to_parquet
|
|
264
293
|
|
|
265
294
|
def __del__(self):
|
mage_ai/io/bigquery.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import traceback
|
|
1
2
|
import uuid
|
|
2
3
|
from typing import Dict, List, Mapping, Union
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
5
7
|
from google.cloud.bigquery import (
|
|
6
8
|
Client,
|
|
7
9
|
LoadJobConfig,
|
|
@@ -399,7 +401,41 @@ WHERE table_id = '{table_name}'
|
|
|
399
401
|
# Clean column names
|
|
400
402
|
if type(df) is DataFrame:
|
|
401
403
|
df.columns = df.columns.str.replace(' ', '_')
|
|
404
|
+
table = None
|
|
405
|
+
try:
|
|
406
|
+
# Cast column types
|
|
407
|
+
table = self.client.get_table(table_id)
|
|
408
|
+
except Exception:
|
|
409
|
+
print(f'Table {table_id} does not exist.')
|
|
410
|
+
pass
|
|
402
411
|
|
|
412
|
+
if table is not None:
|
|
413
|
+
try:
|
|
414
|
+
timestamp_columns = [field.name for field in table.schema
|
|
415
|
+
if field.field_type == 'TIMESTAMP']
|
|
416
|
+
|
|
417
|
+
# Convert TIMESTAMP columns in DataFrame
|
|
418
|
+
for col in timestamp_columns:
|
|
419
|
+
if col in df.columns:
|
|
420
|
+
df[col] = pd.to_datetime(df[col])
|
|
421
|
+
except Exception:
|
|
422
|
+
print('Fail to cast column types in dataframe.')
|
|
423
|
+
traceback.print_exc()
|
|
424
|
+
if (
|
|
425
|
+
not config.schema and
|
|
426
|
+
config.write_disposition == WriteDisposition.WRITE_TRUNCATE
|
|
427
|
+
):
|
|
428
|
+
df_columns = df.columns.tolist()
|
|
429
|
+
config.schema = [
|
|
430
|
+
SchemaField(
|
|
431
|
+
field.name,
|
|
432
|
+
field.field_type,
|
|
433
|
+
mode=field.mode,
|
|
434
|
+
fields=field.fields,
|
|
435
|
+
)
|
|
436
|
+
for field in table.schema
|
|
437
|
+
if field.name in df_columns
|
|
438
|
+
]
|
|
403
439
|
return self.client.load_table_from_dataframe(df, table_id, job_config=config).result()
|
|
404
440
|
|
|
405
441
|
def execute(self, query_string: str, **kwargs) -> None:
|
mage_ai/io/config.py
CHANGED
|
@@ -16,6 +16,8 @@ class ConfigKey(StrEnum):
|
|
|
16
16
|
List of configuration settings for use with data IO clients.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
AIRTABLE_ACCESS_TOKEN = "AIRTABLE_ACCESS_TOKEN"
|
|
20
|
+
|
|
19
21
|
ALGOLIA_APP_ID = 'ALGOLIA_APP_ID'
|
|
20
22
|
ALGOLIA_API_KEY = 'ALGOLIA_API_KEY'
|
|
21
23
|
ALGOLIA_INDEX_NAME = 'ALGOLIA_INDEX_NAME'
|
|
@@ -78,6 +80,7 @@ class ConfigKey(StrEnum):
|
|
|
78
80
|
MYSQL_PASSWORD = 'MYSQL_PASSWORD'
|
|
79
81
|
MYSQL_PORT = 'MYSQL_PORT'
|
|
80
82
|
MYSQL_USER = 'MYSQL_USER'
|
|
83
|
+
MYSQL_ALLOW_LOCAL_INFILE = 'MYSQL_ALLOW_LOCAL_INFILE'
|
|
81
84
|
|
|
82
85
|
ORACLEDB_USER = 'ORACLEDB_USER'
|
|
83
86
|
ORACLEDB_PASSWORD = 'ORACLEDB_PASSWORD'
|
|
@@ -338,6 +341,7 @@ class VerboseConfigKey(StrEnum):
|
|
|
338
341
|
Config key headers for the verbose configuration file format.
|
|
339
342
|
"""
|
|
340
343
|
|
|
344
|
+
AIRTABLE = 'Airtable'
|
|
341
345
|
ALGOLIA = 'Algolia'
|
|
342
346
|
AWS = 'AWS'
|
|
343
347
|
BIGQUERY = 'BigQuery'
|
|
@@ -356,6 +360,7 @@ class VerboseConfigKey(StrEnum):
|
|
|
356
360
|
|
|
357
361
|
class ConfigFileLoader(BaseConfigLoader):
|
|
358
362
|
KEY_MAP = {
|
|
363
|
+
ConfigKey.AIRTABLE_ACCESS_TOKEN: VerboseConfigKey.AIRTABLE,
|
|
359
364
|
ConfigKey.ALGOLIA_APP_ID: (
|
|
360
365
|
VerboseConfigKey.ALGOLIA, 'app_id'),
|
|
361
366
|
ConfigKey.ALGOLIA_API_KEY: (
|
mage_ai/io/mssql.py
CHANGED
|
@@ -14,6 +14,7 @@ from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
|
14
14
|
from mage_ai.io.constants import UNIQUE_CONFLICT_METHOD_UPDATE
|
|
15
15
|
from mage_ai.io.export_utils import PandasTypes
|
|
16
16
|
from mage_ai.io.sql import BaseSQL
|
|
17
|
+
from mage_ai.shared.hash import extract
|
|
17
18
|
from mage_ai.shared.parsers import encode_complex
|
|
18
19
|
|
|
19
20
|
MERGE_TABLE_SQL = '''MERGE {table_name} AS t
|
|
@@ -197,9 +198,11 @@ class MSSQL(BaseSQL):
|
|
|
197
198
|
TrustServerCertificate='yes',
|
|
198
199
|
),
|
|
199
200
|
)
|
|
201
|
+
conn_kwargs = extract(kwargs, ['pool_size', 'max_overflow'])
|
|
200
202
|
engine = create_engine(
|
|
201
203
|
connection_url,
|
|
202
204
|
fast_executemany=True,
|
|
205
|
+
**conn_kwargs,
|
|
203
206
|
)
|
|
204
207
|
|
|
205
208
|
unique_conflict_method = kwargs.get('unique_conflict_method')
|
|
@@ -242,6 +245,7 @@ class MSSQL(BaseSQL):
|
|
|
242
245
|
method=merge_table,
|
|
243
246
|
)
|
|
244
247
|
return
|
|
248
|
+
sql_kwargs = extract(kwargs, ['chunksize', 'method'])
|
|
245
249
|
|
|
246
250
|
df.to_sql(
|
|
247
251
|
table_name,
|
|
@@ -249,6 +253,7 @@ class MSSQL(BaseSQL):
|
|
|
249
253
|
schema=schema_name,
|
|
250
254
|
if_exists=if_exists or ExportWritePolicy.REPLACE,
|
|
251
255
|
index=False,
|
|
256
|
+
**sql_kwargs,
|
|
252
257
|
)
|
|
253
258
|
|
|
254
259
|
def get_type(self, column: Series, dtype: str) -> str:
|
mage_ai/io/mysql.py
CHANGED
|
@@ -25,6 +25,7 @@ class MySQL(BaseSQL):
|
|
|
25
25
|
password: str,
|
|
26
26
|
user: str,
|
|
27
27
|
port: int = 3306,
|
|
28
|
+
allow_local_infile: bool = False,
|
|
28
29
|
verbose: bool = True,
|
|
29
30
|
**kwargs,
|
|
30
31
|
) -> None:
|
|
@@ -35,18 +36,22 @@ class MySQL(BaseSQL):
|
|
|
35
36
|
port=port or 3306,
|
|
36
37
|
user=user,
|
|
37
38
|
verbose=verbose,
|
|
39
|
+
allow_local_infile=allow_local_infile,
|
|
38
40
|
**kwargs,
|
|
39
41
|
)
|
|
40
42
|
|
|
41
43
|
@classmethod
|
|
42
44
|
def with_config(cls, config: BaseConfigLoader) -> 'MySQL':
|
|
43
|
-
|
|
45
|
+
conn_kwargs = dict(
|
|
44
46
|
database=config[ConfigKey.MYSQL_DATABASE],
|
|
45
47
|
host=config[ConfigKey.MYSQL_HOST],
|
|
46
48
|
password=config[ConfigKey.MYSQL_PASSWORD],
|
|
47
49
|
port=config[ConfigKey.MYSQL_PORT],
|
|
48
50
|
user=config[ConfigKey.MYSQL_USER],
|
|
49
51
|
)
|
|
52
|
+
if config[ConfigKey.MYSQL_ALLOW_LOCAL_INFILE] is not None:
|
|
53
|
+
conn_kwargs['allow_local_infile'] = config[ConfigKey.MYSQL_ALLOW_LOCAL_INFILE]
|
|
54
|
+
return cls(**conn_kwargs)
|
|
50
55
|
|
|
51
56
|
def build_create_table_command(
|
|
52
57
|
self,
|
mage_ai/io/oracledb.py
CHANGED
|
@@ -244,10 +244,8 @@ FETCH FIRST {limit} ROWS ONLY
|
|
|
244
244
|
# Remove extraneous surrounding double quotes
|
|
245
245
|
# that get added while performing conversion to string.
|
|
246
246
|
df_[col] = df_[col].apply(lambda x: x.strip('"') if x and isinstance(x, str) else x)
|
|
247
|
-
df_.
|
|
248
|
-
values =
|
|
249
|
-
for i in range(0, len(df_)):
|
|
250
|
-
values.append(tuple(df_.fillna('').values[i]))
|
|
247
|
+
df_.fillna('', inplace=True)
|
|
248
|
+
values = list(df_.itertuples(index=False, name=None))
|
|
251
249
|
|
|
252
250
|
# Create values placeholder
|
|
253
251
|
colmn_names = df.columns.tolist()
|
mage_ai/io/postgres.py
CHANGED
|
@@ -99,6 +99,8 @@ class Postgres(BaseSQL):
|
|
|
99
99
|
password = self.settings['password']
|
|
100
100
|
port = self.settings['port']
|
|
101
101
|
user = self.settings['user']
|
|
102
|
+
keepalives = self.settings.get('keepalives', 1)
|
|
103
|
+
keepalives_idle = self.settings.get('keepalives_idle', 300)
|
|
102
104
|
if self.settings['connection_method'] == 'ssh_tunnel':
|
|
103
105
|
ssh_setting = dict(ssh_username=self.settings['ssh_username'])
|
|
104
106
|
if self.settings['ssh_pkey'] is not None:
|
|
@@ -128,19 +130,35 @@ class Postgres(BaseSQL):
|
|
|
128
130
|
host = '127.0.0.1'
|
|
129
131
|
port = self.ssh_tunnel.local_bind_port
|
|
130
132
|
|
|
131
|
-
connect_opts =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
connect_opts = self.settings.copy()
|
|
134
|
+
# See recognized keyword parameters for psycopg2.connect()
|
|
135
|
+
# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
|
|
136
|
+
unrecognized_keys = [
|
|
137
|
+
'dbname',
|
|
138
|
+
'schema',
|
|
139
|
+
'connection_method',
|
|
140
|
+
'ssh_host',
|
|
141
|
+
'ssh_port',
|
|
142
|
+
'ssh_username',
|
|
143
|
+
'ssh_password',
|
|
144
|
+
'ssh_pkey',
|
|
145
|
+
"verbose"
|
|
146
|
+
]
|
|
147
|
+
for key in unrecognized_keys:
|
|
148
|
+
connect_opts.pop(key, None)
|
|
149
|
+
|
|
150
|
+
connect_opts.update(
|
|
151
|
+
{
|
|
152
|
+
'database': database,
|
|
153
|
+
'host': host,
|
|
154
|
+
'password': password,
|
|
155
|
+
'port': port,
|
|
156
|
+
'user': user,
|
|
157
|
+
'keepalives': keepalives,
|
|
158
|
+
'keepalives_idle': keepalives_idle,
|
|
159
|
+
}
|
|
139
160
|
)
|
|
140
161
|
|
|
141
|
-
if self.settings.get('connect_timeout'):
|
|
142
|
-
connect_opts['connect_timeout'] = self.settings['connect_timeout']
|
|
143
|
-
|
|
144
162
|
try:
|
|
145
163
|
self._ctx = connect(**connect_opts)
|
|
146
164
|
except Exception:
|
|
@@ -277,6 +295,14 @@ class Postgres(BaseSQL):
|
|
|
277
295
|
|
|
278
296
|
return 'text'
|
|
279
297
|
|
|
298
|
+
@staticmethod
|
|
299
|
+
def _clean_array_value(val: str) -> str:
|
|
300
|
+
if val is None or type(val) is not str or len(val) < 2:
|
|
301
|
+
return val
|
|
302
|
+
if val[0] == '[' and val[-1] == ']':
|
|
303
|
+
return str(val).replace('[', '{').replace(']', '}')
|
|
304
|
+
return val
|
|
305
|
+
|
|
280
306
|
def upload_dataframe(
|
|
281
307
|
self,
|
|
282
308
|
cursor: _psycopg.cursor,
|
|
@@ -298,13 +324,6 @@ class Postgres(BaseSQL):
|
|
|
298
324
|
# Use COPY command
|
|
299
325
|
use_insert_command = False
|
|
300
326
|
|
|
301
|
-
def clean_array_value(val):
|
|
302
|
-
if val is None or type(val) is not str or len(val) < 2:
|
|
303
|
-
return val
|
|
304
|
-
if val[0] == '[' and val[-1] == ']':
|
|
305
|
-
return '{' + val[1:-1] + '}'
|
|
306
|
-
return val
|
|
307
|
-
|
|
308
327
|
def serialize_obj(val):
|
|
309
328
|
if type(val) is dict or type(val) is np.ndarray:
|
|
310
329
|
return simplejson.dumps(
|
|
@@ -319,7 +338,7 @@ class Postgres(BaseSQL):
|
|
|
319
338
|
ignore_nan=True,
|
|
320
339
|
)
|
|
321
340
|
elif not use_insert_command and type(val) is list:
|
|
322
|
-
return
|
|
341
|
+
return self._clean_array_value(simplejson.dumps(
|
|
323
342
|
val,
|
|
324
343
|
default=encode_complex,
|
|
325
344
|
ignore_nan=True,
|
mage_ai/io/redshift.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Dict, Union
|
|
|
5
5
|
from pandas import DataFrame
|
|
6
6
|
from redshift_connector import connect
|
|
7
7
|
|
|
8
|
+
from mage_ai.data_preparation.models.block.sql.utils.shared import split_query_string
|
|
8
9
|
from mage_ai.io.base import QUERY_ROW_LIMIT, ExportWritePolicy
|
|
9
10
|
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
10
11
|
from mage_ai.io.export_utils import clean_df_for_export, infer_dtypes
|
|
@@ -67,6 +68,18 @@ class Redshift(BaseSQL):
|
|
|
67
68
|
with self.conn.cursor() as cur:
|
|
68
69
|
cur.execute(query_string, **kwargs)
|
|
69
70
|
|
|
71
|
+
def execute_query_raw(self, query: str, **kwargs) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Overwrite execute query to process multiple queries in one string.
|
|
74
|
+
"""
|
|
75
|
+
results = []
|
|
76
|
+
with self.conn.cursor() as cursor:
|
|
77
|
+
for query_string in split_query_string(query):
|
|
78
|
+
result = cursor.execute(query_string)
|
|
79
|
+
results.append(result)
|
|
80
|
+
self.conn.commit()
|
|
81
|
+
return results
|
|
82
|
+
|
|
70
83
|
def load(
|
|
71
84
|
self,
|
|
72
85
|
query_string: str,
|
mage_ai/io/sql.py
CHANGED
mage_ai/io/utils.py
CHANGED
|
@@ -29,3 +29,21 @@ def escape_quotes(line: str, single: bool = True, double: bool = True) -> str:
|
|
|
29
29
|
if double:
|
|
30
30
|
new_line = new_line.replace('\"', '\\"')
|
|
31
31
|
return new_line
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def map_json_to_airtable(data_types):
|
|
35
|
+
# Extract the non-null type (ignoring 'null')
|
|
36
|
+
data_type = next((t for t in data_types if t != 'null'), 'string')
|
|
37
|
+
|
|
38
|
+
# Mapping from JSON types to Airtable types
|
|
39
|
+
type_mapping = {
|
|
40
|
+
'string': 'multilineText',
|
|
41
|
+
'integer': 'number',
|
|
42
|
+
'boolean': 'checkbox',
|
|
43
|
+
'array': 'multipleSelects',
|
|
44
|
+
'object': 'singleCollaborator',
|
|
45
|
+
'number': 'number',
|
|
46
|
+
'date-time': 'dateTime'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return type_mapping.get(data_type, 'str')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
+
from functools import wraps
|
|
3
4
|
from urllib.parse import parse_qs, quote_plus, urlparse
|
|
4
5
|
|
|
5
6
|
import sqlalchemy
|
|
@@ -13,7 +14,7 @@ from mage_ai.orchestration.db.setup import get_postgres_connection_url
|
|
|
13
14
|
from mage_ai.orchestration.db.utils import get_user_info_from_db_connection_url
|
|
14
15
|
from mage_ai.settings import OTEL_EXPORTER_OTLP_ENDPOINT
|
|
15
16
|
from mage_ai.settings.repo import get_variables_dir
|
|
16
|
-
from mage_ai.shared.environments import is_debug,
|
|
17
|
+
from mage_ai.shared.environments import is_debug, is_test_mage
|
|
17
18
|
|
|
18
19
|
DB_RETRY_COUNT = 2
|
|
19
20
|
TEST_DB = 'test.db'
|
|
@@ -28,7 +29,7 @@ db_kwargs = dict(
|
|
|
28
29
|
if OTEL_EXPORTER_OTLP_ENDPOINT:
|
|
29
30
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
30
31
|
|
|
31
|
-
if
|
|
32
|
+
if is_test_mage():
|
|
32
33
|
db_connection_url = f'sqlite:///{TEST_DB}'
|
|
33
34
|
db_kwargs['connect_args']['check_same_thread'] = False
|
|
34
35
|
elif not db_connection_url:
|
|
@@ -37,7 +38,7 @@ elif not db_connection_url:
|
|
|
37
38
|
if pg_db_connection_url:
|
|
38
39
|
db_connection_url = pg_db_connection_url
|
|
39
40
|
else:
|
|
40
|
-
if
|
|
41
|
+
if is_test_mage():
|
|
41
42
|
db_connection_url = f'sqlite:///{TEST_DB}'
|
|
42
43
|
elif os.path.exists(os.path.join('mage_ai', 'orchestration', 'db')):
|
|
43
44
|
# For local dev environment
|
|
@@ -169,6 +170,25 @@ def safe_db_query(func):
|
|
|
169
170
|
return func_with_rollback
|
|
170
171
|
|
|
171
172
|
|
|
173
|
+
def safe_db_query_async(func):
|
|
174
|
+
@wraps(func)
|
|
175
|
+
async def func_with_rollback_async(*args, **kwargs):
|
|
176
|
+
retry_count = 0
|
|
177
|
+
while True:
|
|
178
|
+
try:
|
|
179
|
+
return await func(*args, **kwargs)
|
|
180
|
+
except (
|
|
181
|
+
sqlalchemy.exc.OperationalError,
|
|
182
|
+
sqlalchemy.exc.PendingRollbackError,
|
|
183
|
+
sqlalchemy.exc.InternalError,
|
|
184
|
+
) as e:
|
|
185
|
+
db_connection.session.rollback()
|
|
186
|
+
if retry_count >= DB_RETRY_COUNT:
|
|
187
|
+
raise e
|
|
188
|
+
retry_count += 1
|
|
189
|
+
return func_with_rollback_async
|
|
190
|
+
|
|
191
|
+
|
|
172
192
|
logging.basicConfig()
|
|
173
193
|
|
|
174
194
|
if is_debug() and not os.getenv('DISABLE_DATABASE_TERMINAL_OUTPUT'):
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import collections
|
|
3
2
|
import traceback
|
|
4
3
|
import uuid
|
|
@@ -1400,10 +1399,8 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
|
|
|
1400
1399
|
|
|
1401
1400
|
from mage_ai.usage_statistics.logger import UsageStatisticLogger
|
|
1402
1401
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
lambda: self.query.filter(self.status == self.PipelineRunStatus.COMPLETED).count(),
|
|
1406
|
-
)
|
|
1402
|
+
UsageStatisticLogger().pipeline_runs_impression_sync(
|
|
1403
|
+
lambda: self.query.filter(self.status == self.PipelineRunStatus.COMPLETED).count(),
|
|
1407
1404
|
)
|
|
1408
1405
|
|
|
1409
1406
|
@safe_db_query
|
|
@@ -3,7 +3,8 @@ from typing import Optional
|
|
|
3
3
|
from sqlalchemy import Column, String, Text, UniqueConstraint, or_
|
|
4
4
|
|
|
5
5
|
from mage_ai.orchestration.db import safe_db_query
|
|
6
|
-
from mage_ai.orchestration.db.models.base import BaseModel
|
|
6
|
+
from mage_ai.orchestration.db.models.base import BaseModel, classproperty
|
|
7
|
+
from mage_ai.settings.repo import get_repo_path
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Secret(BaseModel):
|
|
@@ -15,6 +16,15 @@ class Secret(BaseModel):
|
|
|
15
16
|
key_uuid = Column(String(255), nullable=True)
|
|
16
17
|
__table_args__ = (UniqueConstraint('name', 'key_uuid', name='name_key_uuid_uc'),)
|
|
17
18
|
|
|
19
|
+
@classproperty
|
|
20
|
+
def repo_query(cls):
|
|
21
|
+
return cls.query.filter(
|
|
22
|
+
or_(
|
|
23
|
+
Secret.repo_name == get_repo_path(),
|
|
24
|
+
Secret.repo_name.is_(None),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
18
28
|
@classmethod
|
|
19
29
|
@safe_db_query
|
|
20
30
|
def get_secret(cls, name: str, key_uuid: str) -> Optional['Secret']:
|
|
@@ -220,7 +220,7 @@ def __calculate_metrics(pipeline_run: PipelineRun, pipeline) -> Dict:
|
|
|
220
220
|
|
|
221
221
|
pipeline_metrics_by_stream = {}
|
|
222
222
|
pipeline_logs_by_stream = {}
|
|
223
|
-
pipeline_logs = pipeline_run.logs['content'].split('\n')
|
|
223
|
+
pipeline_logs = pipeline_run.logs[0]['content'].split('\n')
|
|
224
224
|
for pipeline_log in pipeline_logs:
|
|
225
225
|
tags = parse_line(pipeline_log)
|
|
226
226
|
stream = tags.get('stream')
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import collections
|
|
3
2
|
import os
|
|
4
3
|
import traceback
|
|
@@ -260,7 +259,7 @@ class PipelineScheduler:
|
|
|
260
259
|
pipeline=self.pipeline,
|
|
261
260
|
pipeline_run=self.pipeline_run,
|
|
262
261
|
)
|
|
263
|
-
|
|
262
|
+
UsageStatisticLogger().pipeline_run_ended_sync(self.pipeline_run)
|
|
264
263
|
|
|
265
264
|
self.logger_manager.output_logs_to_destination()
|
|
266
265
|
|
|
@@ -339,7 +338,7 @@ class PipelineScheduler:
|
|
|
339
338
|
error_msg: str,
|
|
340
339
|
status=PipelineRun.PipelineRunStatus.FAILED,
|
|
341
340
|
) -> None:
|
|
342
|
-
|
|
341
|
+
UsageStatisticLogger().pipeline_run_ended_sync(self.pipeline_run)
|
|
343
342
|
|
|
344
343
|
if status == PipelineRun.PipelineRunStatus.FAILED:
|
|
345
344
|
# Only send notification when pipeline run status is FAILED
|
|
@@ -1372,7 +1371,7 @@ def stop_pipeline_run(
|
|
|
1372
1371
|
# Update pipeline run status to cancelled
|
|
1373
1372
|
pipeline_run.update(status=status)
|
|
1374
1373
|
|
|
1375
|
-
|
|
1374
|
+
UsageStatisticLogger().pipeline_run_ended_sync(pipeline_run)
|
|
1376
1375
|
|
|
1377
1376
|
# Cancel all the block runs
|
|
1378
1377
|
cancel_block_runs_and_jobs(pipeline_run, pipeline)
|
|
@@ -1584,7 +1583,7 @@ def schedule_all():
|
|
|
1584
1583
|
pipeline_runs_excluded_by_limit = []
|
|
1585
1584
|
for pipeline_schedule in active_schedules:
|
|
1586
1585
|
lock_key = f'pipeline_schedule_{pipeline_schedule.id}'
|
|
1587
|
-
if not lock.try_acquire_lock(lock_key):
|
|
1586
|
+
if not lock.try_acquire_lock(lock_key, timeout=30):
|
|
1588
1587
|
continue
|
|
1589
1588
|
|
|
1590
1589
|
trigger_pipeline_run_limit = pipeline_schedule.get_settings().pipeline_run_limit
|