mage-ai 0.9.65__py3-none-any.whl → 0.9.67__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 +38 -16
- mage_ai/api/policies/BackfillPolicy.py +1 -0
- mage_ai/api/resources/OauthResource.py +13 -5
- mage_ai/api/resources/SessionResource.py +6 -4
- mage_ai/authentication/ldap.py +19 -9
- mage_ai/authentication/oauth/constants.py +8 -14
- mage_ai/authentication/oauth/utils.py +18 -6
- mage_ai/authentication/providers/active_directory.py +21 -16
- mage_ai/authentication/providers/azure_devops.py +18 -0
- mage_ai/authentication/providers/bitbucket.py +10 -9
- mage_ai/authentication/providers/constants.py +2 -0
- mage_ai/authentication/providers/ghe.py +5 -9
- mage_ai/authentication/providers/gitlab.py +6 -9
- mage_ai/authentication/providers/google.py +9 -6
- mage_ai/authentication/providers/oidc.py +6 -4
- mage_ai/authentication/providers/okta.py +9 -6
- mage_ai/cluster_manager/kubernetes/workload_manager.py +10 -0
- mage_ai/cluster_manager/workspace/base.py +6 -1
- mage_ai/cluster_manager/workspace/kubernetes.py +3 -0
- mage_ai/data_preparation/decorators.py +15 -0
- mage_ai/data_preparation/executors/streaming_pipeline_executor.py +22 -12
- mage_ai/data_preparation/git/__init__.py +10 -1
- mage_ai/data_preparation/git/api.py +3 -0
- mage_ai/data_preparation/git/clients/azure_devops.py +106 -0
- mage_ai/data_preparation/git/clients/base.py +6 -0
- mage_ai/data_preparation/git/clients/gitlab.py +3 -2
- mage_ai/data_preparation/git/utils.py +31 -29
- mage_ai/data_preparation/models/block/__init__.py +27 -18
- mage_ai/data_preparation/models/block/dbt/block_sql.py +164 -0
- mage_ai/data_preparation/models/block/dynamic/variables.py +1 -2
- mage_ai/data_preparation/models/pipeline.py +3 -3
- mage_ai/data_preparation/models/triggers/__init__.py +6 -1
- mage_ai/data_preparation/preferences.py +42 -37
- mage_ai/data_preparation/repo_manager.py +21 -0
- mage_ai/data_preparation/storage/gcs_storage.py +27 -2
- mage_ai/data_preparation/storage/local_storage.py +18 -3
- mage_ai/data_preparation/storage/s3_storage.py +7 -2
- mage_ai/data_preparation/templates/data_loaders/streaming/generic_python.py +23 -0
- mage_ai/data_preparation/templates/main/metadata.yaml +6 -0
- mage_ai/data_preparation/templates/template.py +6 -2
- mage_ai/data_preparation/variable_manager.py +2 -1
- mage_ai/io/base.py +3 -0
- mage_ai/io/bigquery.py +2 -0
- mage_ai/io/export_utils.py +14 -9
- mage_ai/io/mssql.py +104 -25
- mage_ai/io/mysql.py +10 -9
- mage_ai/io/oracledb.py +14 -2
- mage_ai/io/postgres.py +3 -0
- mage_ai/io/sql.py +14 -6
- mage_ai/io/trino.py +10 -8
- mage_ai/orchestration/db/migrations/versions/90d978a8aef8_update_unique_constraint_for_secret.py +11 -5
- mage_ai/orchestration/db/models/schedules.py +25 -1
- mage_ai/orchestration/db/models/schedules_project_platform.py +24 -1
- mage_ai/orchestration/job_manager.py +6 -1
- mage_ai/orchestration/pipeline_scheduler_original.py +16 -10
- mage_ai/server/constants.py +1 -1
- mage_ai/server/file_observer.py +10 -0
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/_next/static/chunks/{1557-a754b04510d50b80.js → 1557-01f0843dc6ac4971.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/9440-4069842b90d4b801.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/oauth-30e34ee15d410331.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{edit-8d32ac7e8f023779.js → edit-36377e679da2cd91.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-3f5c14076ddde20e.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-fe08120d9a6fb1b0.js → triggers-f508c2f261297724.js} +1 -1
- mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-bcdb4ad41dd4c7d5.js → frontend_dist/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{sign-in-19b36600d908b711.js → sign-in-7d38b2f7c3e918a1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{webpack-ac7fdc472bedf682.js → webpack-d079359c241db804.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/{ZMrJfDouIX5AMb_RteRbL → vPsMu6Fi2zrHaf2fRXKRO}/_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 +2 -2
- mage_ai/server/frontend_dist/overview.html +2 -2
- mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist/pipelines.html +2 -2
- mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
- mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist/settings.html +2 -2
- mage_ai/server/frontend_dist/sign-in.html +2 -2
- 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_base_path_template/_next/static/chunks/{1557-a754b04510d50b80.js → 1557-01f0843dc6ac4971.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9440-4069842b90d4b801.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/oauth-30e34ee15d410331.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{edit-8d32ac7e8f023779.js → edit-36377e679da2cd91.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-3f5c14076ddde20e.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-fe08120d9a6fb1b0.js → triggers-f508c2f261297724.js} +1 -1
- mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines-bcdb4ad41dd4c7d5.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{sign-in-19b36600d908b711.js → sign-in-7d38b2f7c3e918a1.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +1 -0
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{webpack-481689d9989710cd.js → webpack-68c003fb6a175cd7.js} +1 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/{QYwFH4sievaq5XyUjRriy → khKiaJtwrslgMmp4YSa1f}/_buildManifest.js +1 -1
- mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/sign-in.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
- mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
- mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
- mage_ai/server/server.py +117 -87
- mage_ai/server/utils/output_display.py +6 -1
- mage_ai/services/aws/s3/s3.py +8 -2
- mage_ai/services/slack/slack.py +8 -8
- mage_ai/settings/__init__.py +36 -186
- mage_ai/settings/backends.py +95 -0
- mage_ai/settings/keys/__init__.py +1 -0
- mage_ai/settings/keys/auth.py +76 -0
- mage_ai/settings/server.py +187 -0
- mage_ai/shared/io.py +2 -2
- mage_ai/shared/logger.py +12 -6
- mage_ai/streaming/sources/base_python.py +30 -0
- mage_ai/streaming/sources/source_factory.py +25 -0
- mage_ai/tests/api/endpoints/test_oauths.py +13 -5
- mage_ai/tests/api/operations/test_operations.py +2 -2
- mage_ai/tests/api/operations/test_sessions.py +83 -48
- mage_ai/tests/authentication/oauth/test_utils.py +56 -6
- mage_ai/tests/authentication/providers/test_active_directory.py +9 -15
- mage_ai/tests/data_preparation/models/test_block.py +39 -2
- mage_ai/tests/orchestration/db/models/test_schedules.py +33 -1
- {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/METADATA +2 -1
- {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/RECORD +225 -217
- {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/WHEEL +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/9440-2bcbdc765ed82062.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2ae1d919333f01fe.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/index-64851458dde54ad9.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/oauth-abe5ba687cb93509.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-22e49726eeed16ae.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-c74507dce89b41a2.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-cf656cbe37ecaacc.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-690206d30d8b412b.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9440-2bcbdc765ed82062.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2ae1d919333f01fe.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-64851458dde54ad9.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/oauth-abe5ba687cb93509.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-22e49726eeed16ae.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-c74507dce89b41a2.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-cf656cbe37ecaacc.js +0 -1
- mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-690206d30d8b412b.js +0 -1
- mage_ai/settings/sso.py +0 -27
- /mage_ai/server/frontend_dist/_next/static/{ZMrJfDouIX5AMb_RteRbL → vPsMu6Fi2zrHaf2fRXKRO}/_ssgManifest.js +0 -0
- /mage_ai/server/frontend_dist_base_path_template/_next/static/{QYwFH4sievaq5XyUjRriy → khKiaJtwrslgMmp4YSa1f}/_ssgManifest.js +0 -0
- {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/LICENSE +0 -0
- {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.9.65.dist-info → mage_ai-0.9.67.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
6
6
|
|
|
7
7
|
import pandas as pd
|
|
8
|
+
import simplejson
|
|
8
9
|
from jinja2 import Template
|
|
9
10
|
|
|
10
11
|
from mage_ai.data_preparation.models.block import Block
|
|
@@ -27,6 +28,8 @@ from mage_ai.settings.utils import base_repo_path
|
|
|
27
28
|
from mage_ai.shared.files import find_file_from_another_file_path
|
|
28
29
|
from mage_ai.shared.hash import merge_dict
|
|
29
30
|
from mage_ai.shared.path_fixer import add_absolute_path
|
|
31
|
+
from mage_ai.shared.strings import remove_extension_from_filename
|
|
32
|
+
from mage_ai.shared.utils import clean_name
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class DBTBlockSQL(DBTBlock, ProjectPlatformAccessible):
|
|
@@ -136,6 +139,167 @@ class DBTBlockSQL(DBTBlock, ProjectPlatformAccessible):
|
|
|
136
139
|
|
|
137
140
|
return search_paths.get(node_path_type_part)
|
|
138
141
|
|
|
142
|
+
def upstream_dbt_blocks(self, read_only=False) -> List['DBTBlockSQL']:
|
|
143
|
+
"""
|
|
144
|
+
Get an up to date list, which represents the upstream dbt graph.
|
|
145
|
+
It is using `dbt list` to generate the list.
|
|
146
|
+
Args:
|
|
147
|
+
read_only (bool):
|
|
148
|
+
If True it does not read the Blocks from the model. Defaults to False
|
|
149
|
+
Returns:
|
|
150
|
+
List[DBTBlockSQL]: THe upstream dbt graph as DBTBlocksSQL objects
|
|
151
|
+
"""
|
|
152
|
+
# Get upstream nodes via dbt list
|
|
153
|
+
with Profiles(
|
|
154
|
+
self.project_path,
|
|
155
|
+
self.pipeline.variables if self.pipeline else {},
|
|
156
|
+
) as profiles:
|
|
157
|
+
try:
|
|
158
|
+
args = [
|
|
159
|
+
'list',
|
|
160
|
+
# project-dir
|
|
161
|
+
# /home/src/default_repo/default_platform/default_repo/dbt/demo
|
|
162
|
+
'--project-dir', self.project_path,
|
|
163
|
+
'--profiles-dir', str(profiles.profiles_dir),
|
|
164
|
+
'--select', '+' + Path(self.file_path).stem,
|
|
165
|
+
'--output', 'json',
|
|
166
|
+
'--output-keys', 'unique_id original_file_path depends_on',
|
|
167
|
+
'--resource-type', 'model',
|
|
168
|
+
'--resource-type', 'snapshot'
|
|
169
|
+
]
|
|
170
|
+
res = DBTCli().invoke(args)
|
|
171
|
+
|
|
172
|
+
if res:
|
|
173
|
+
nodes_init = [simplejson.loads(node) for node in res.result]
|
|
174
|
+
else:
|
|
175
|
+
return []
|
|
176
|
+
except Exception as err:
|
|
177
|
+
print(f'[ERROR] DBTBlockSQL.upstream_dbt_blocks: {err}.')
|
|
178
|
+
return [
|
|
179
|
+
self.build_dbt_block(
|
|
180
|
+
block_class=DBTBlock,
|
|
181
|
+
block_dict=dict(
|
|
182
|
+
block_type=self.type,
|
|
183
|
+
configuration=self.configuration,
|
|
184
|
+
language=self.language,
|
|
185
|
+
name=self.uuid,
|
|
186
|
+
pipeline=self.pipeline,
|
|
187
|
+
uuid=self.uuid,
|
|
188
|
+
),
|
|
189
|
+
hydrate_configuration=False,
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
# transform List into dict and remove unnecessary fields
|
|
194
|
+
file_path = self.file_path
|
|
195
|
+
path_parts = file_path.split(os.sep)
|
|
196
|
+
project_dir = path_parts[0]
|
|
197
|
+
|
|
198
|
+
nodes_default = {
|
|
199
|
+
node['unique_id']: {
|
|
200
|
+
# file_path:
|
|
201
|
+
# default_repo/dbt/demo/models/example/model.sql
|
|
202
|
+
# default_platform/default_repo/dbt/demo/models/example/model.sql
|
|
203
|
+
'file_path': os.path.join(project_dir, node['original_file_path']),
|
|
204
|
+
'original_file_path': node['original_file_path'],
|
|
205
|
+
'upstream_nodes': set(node['depends_on']['nodes'])
|
|
206
|
+
}
|
|
207
|
+
for node in nodes_init
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
nodes = self.hydrate_dbt_nodes(nodes_default, nodes_init)
|
|
211
|
+
|
|
212
|
+
# calculate downstream_nodes
|
|
213
|
+
for unique_id, node in nodes.items():
|
|
214
|
+
for upstream_node in node['upstream_nodes']:
|
|
215
|
+
if nodes.get(upstream_node):
|
|
216
|
+
downstream_nodes = nodes[upstream_node].get('downstream_nodes', set())
|
|
217
|
+
downstream_nodes.add(unique_id)
|
|
218
|
+
nodes[upstream_node]['downstream_nodes'] = downstream_nodes
|
|
219
|
+
|
|
220
|
+
# map dbt unique_id to mage uuid
|
|
221
|
+
uuids_default = {
|
|
222
|
+
unique_id: clean_name(
|
|
223
|
+
remove_extension_from_filename(node['file_path']),
|
|
224
|
+
allow_characters=[os.sep]
|
|
225
|
+
)
|
|
226
|
+
for unique_id, node in nodes.items()
|
|
227
|
+
}
|
|
228
|
+
uuids = self.node_uuids_mapping(uuids_default, nodes)
|
|
229
|
+
|
|
230
|
+
# replace dbt unique_ids with mage uuids
|
|
231
|
+
nodes = {
|
|
232
|
+
uuids[unique_id]: {
|
|
233
|
+
'file_path': node['file_path'],
|
|
234
|
+
'original_file_path': node['original_file_path'],
|
|
235
|
+
'upstream_nodes': {
|
|
236
|
+
uuids[upstream_node]
|
|
237
|
+
for upstream_node in node.get('upstream_nodes', set())
|
|
238
|
+
if uuids.get(upstream_node)
|
|
239
|
+
},
|
|
240
|
+
'downstream_nodes': {
|
|
241
|
+
uuids[downstream_node]
|
|
242
|
+
for downstream_node in node.get('downstream_nodes', set())
|
|
243
|
+
if uuids.get(downstream_node)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for unique_id, node in nodes.items()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# create dict of blocks
|
|
250
|
+
blocks = {}
|
|
251
|
+
for uuid, node in nodes.items():
|
|
252
|
+
block = None
|
|
253
|
+
if not read_only:
|
|
254
|
+
if uuid == self.uuid:
|
|
255
|
+
block = self
|
|
256
|
+
elif self.pipeline:
|
|
257
|
+
block = self.pipeline.get_block(
|
|
258
|
+
uuid,
|
|
259
|
+
self.type,
|
|
260
|
+
)
|
|
261
|
+
# if not found create the block
|
|
262
|
+
block = block or self.build_dbt_block(
|
|
263
|
+
block_class=DBTBlock,
|
|
264
|
+
block_dict=dict(
|
|
265
|
+
block_type=self.type,
|
|
266
|
+
configuration=dict(file_path=node['file_path']),
|
|
267
|
+
language=self.language,
|
|
268
|
+
name=uuid,
|
|
269
|
+
pipeline=self.pipeline,
|
|
270
|
+
uuid=uuid,
|
|
271
|
+
),
|
|
272
|
+
node=node,
|
|
273
|
+
)
|
|
274
|
+
# reset upstream dbt blocks
|
|
275
|
+
block.upstream_blocks = [
|
|
276
|
+
block
|
|
277
|
+
for block in block.upstream_blocks
|
|
278
|
+
# there must not be any other upstream dbt blocks that are not part of
|
|
279
|
+
# the upstream nodes, therefore delete all upstream dbt blocks
|
|
280
|
+
if not isinstance(block, DBTBlockSQL)
|
|
281
|
+
]
|
|
282
|
+
# reset downstream dbt blocks
|
|
283
|
+
block.downstream_blocks = [
|
|
284
|
+
block
|
|
285
|
+
for block in block.downstream_blocks
|
|
286
|
+
# remove all downstream dbt block, which are part of the upstream nodes,
|
|
287
|
+
# in order to fix errors
|
|
288
|
+
if block.uuid not in nodes.keys()
|
|
289
|
+
]
|
|
290
|
+
blocks[uuid] = block
|
|
291
|
+
|
|
292
|
+
# Update upstream and downstream dbt blocks
|
|
293
|
+
for uuid, node in nodes.items():
|
|
294
|
+
for upstream_node in node.get('upstream_nodes', set()):
|
|
295
|
+
blocks[uuid].upstream_blocks.append(blocks[upstream_node])
|
|
296
|
+
for downstream_node in node.get('downstream_nodes', set()):
|
|
297
|
+
blocks[uuid].downstream_blocks.append(blocks[downstream_node])
|
|
298
|
+
|
|
299
|
+
# transform into list
|
|
300
|
+
blocks = [block for _, block in blocks.items()]
|
|
301
|
+
return blocks
|
|
302
|
+
|
|
139
303
|
def _execute_block(
|
|
140
304
|
self,
|
|
141
305
|
outputs_from_input_vars,
|
|
@@ -522,9 +522,8 @@ def fetch_input_variables_for_dynamic_upstream_blocks(
|
|
|
522
522
|
global_vars=global_vars,
|
|
523
523
|
**kwargs,
|
|
524
524
|
)
|
|
525
|
-
|
|
526
525
|
input_vars.append(ir[0])
|
|
527
|
-
kwargs_vars.append(kr[0])
|
|
526
|
+
kwargs_vars.append(kr[0] if len(kr) > 0 else None)
|
|
528
527
|
upstream_block_uuids.append(up[0])
|
|
529
528
|
|
|
530
529
|
return input_vars, kwargs_vars, upstream_block_uuids
|
|
@@ -1443,7 +1443,7 @@ class Pipeline:
|
|
|
1443
1443
|
block['downstream_blocks'] = hierarchy_list[b_index]['downstream_blocks']
|
|
1444
1444
|
|
|
1445
1445
|
# dump new config information back in temp folder
|
|
1446
|
-
with open(config_zip_path, 'w') as pipeline_config:
|
|
1446
|
+
with open(config_zip_path, 'w', encoding='utf-8') as pipeline_config:
|
|
1447
1447
|
yaml.dump(config, pipeline_config)
|
|
1448
1448
|
|
|
1449
1449
|
return files_to_be_written, config
|
|
@@ -2141,12 +2141,12 @@ class Pipeline:
|
|
|
2141
2141
|
content = yaml.dump(pipeline_dict)
|
|
2142
2142
|
|
|
2143
2143
|
test_path = f'{self.config_path}.test'
|
|
2144
|
-
async with aiofiles.open(test_path, mode='w') as fp:
|
|
2144
|
+
async with aiofiles.open(test_path, mode='w', encoding='utf-8') as fp:
|
|
2145
2145
|
await fp.write(content)
|
|
2146
2146
|
|
|
2147
2147
|
if os.path.isfile(test_path):
|
|
2148
2148
|
success = True
|
|
2149
|
-
with open(test_path, mode='r') as fp:
|
|
2149
|
+
with open(test_path, mode='r', encoding='utf-8') as fp:
|
|
2150
2150
|
try:
|
|
2151
2151
|
yaml.full_load(fp)
|
|
2152
2152
|
except yaml.scanner.ScannerError:
|
|
@@ -69,6 +69,7 @@ class Trigger(BaseConfig):
|
|
|
69
69
|
sla: int = None # in seconds
|
|
70
70
|
settings: Dict = field(default_factory=dict)
|
|
71
71
|
envs: List = field(default_factory=list)
|
|
72
|
+
repo_path: str = None
|
|
72
73
|
|
|
73
74
|
def __post_init__(self):
|
|
74
75
|
if self.schedule_type and type(self.schedule_type) is str:
|
|
@@ -163,6 +164,7 @@ def build_triggers(
|
|
|
163
164
|
trigger_configs: Dict,
|
|
164
165
|
pipeline_uuid: str = None,
|
|
165
166
|
raise_exception: bool = False,
|
|
167
|
+
repo_path: str = None,
|
|
166
168
|
) -> List[Trigger]:
|
|
167
169
|
triggers = []
|
|
168
170
|
for trigger_config in trigger_configs:
|
|
@@ -170,6 +172,8 @@ def build_triggers(
|
|
|
170
172
|
trigger_config['pipeline_uuid'] = pipeline_uuid
|
|
171
173
|
try:
|
|
172
174
|
trigger = Trigger.load(config=trigger_config)
|
|
175
|
+
if trigger.repo_path is None:
|
|
176
|
+
trigger.repo_path = repo_path
|
|
173
177
|
|
|
174
178
|
# Add flag to settings so frontend can detect triggers with invalid cron expressions
|
|
175
179
|
if not trigger.has_valid_schedule_interval:
|
|
@@ -194,7 +198,8 @@ def load_trigger_configs(
|
|
|
194
198
|
yaml_config = yaml.safe_load(content) or {}
|
|
195
199
|
trigger_configs = yaml_config.get('triggers') or {}
|
|
196
200
|
|
|
197
|
-
return build_triggers(
|
|
201
|
+
return build_triggers(
|
|
202
|
+
trigger_configs, pipeline_uuid, raise_exception, repo_path=get_repo_path())
|
|
198
203
|
|
|
199
204
|
|
|
200
205
|
def add_or_update_trigger_for_pipeline_and_persist(
|
|
@@ -6,51 +6,49 @@ import yaml
|
|
|
6
6
|
|
|
7
7
|
from mage_ai.data_preparation.models.constants import PREFERENCES_FILE
|
|
8
8
|
from mage_ai.orchestration.db.models.oauth import User
|
|
9
|
-
from mage_ai.settings import get_bool_value
|
|
9
|
+
from mage_ai.settings import get_bool_value, get_settings_value
|
|
10
|
+
from mage_ai.settings.keys import (
|
|
11
|
+
GIT_AUTH_TYPE,
|
|
12
|
+
GIT_BRANCH,
|
|
13
|
+
GIT_EMAIL,
|
|
14
|
+
GIT_ENABLE_GIT_INTEGRATION,
|
|
15
|
+
GIT_OVERWRITE_WITH_PROJECT_SETTINGS,
|
|
16
|
+
GIT_REPO_LINK,
|
|
17
|
+
GIT_REPO_PATH,
|
|
18
|
+
GIT_SYNC_ON_EXECUTOR_START,
|
|
19
|
+
GIT_SYNC_ON_PIPELINE_RUN,
|
|
20
|
+
GIT_SYNC_ON_START,
|
|
21
|
+
GIT_SYNC_SUBMODULES,
|
|
22
|
+
GIT_USERNAME,
|
|
23
|
+
)
|
|
10
24
|
from mage_ai.settings.repo import get_repo_path
|
|
11
25
|
from mage_ai.shared.hash import merge_dict
|
|
12
26
|
|
|
13
|
-
# Git environment variables
|
|
14
|
-
GIT_REPO_LINK_VAR = 'GIT_REPO_LINK'
|
|
15
|
-
GIT_REPO_PATH_VAR = 'GIT_REPO_PATH'
|
|
16
|
-
GIT_USERNAME_VAR = 'GIT_USERNAME'
|
|
17
|
-
GIT_EMAIL_VAR = 'GIT_EMAIL'
|
|
18
|
-
GIT_AUTH_TYPE_VAR = 'GIT_AUTH_TYPE'
|
|
19
|
-
GIT_BRANCH_VAR = 'GIT_BRANCH'
|
|
20
|
-
GIT_SYNC_ON_PIPELINE_RUN_VAR = 'GIT_SYNC_ON_PIPELINE_RUN'
|
|
21
|
-
GIT_SYNC_ON_START_VAR = 'GIT_SYNC_ON_START'
|
|
22
|
-
GIT_SYNC_ON_EXECUTOR_START_VAR = 'GIT_SYNC_ON_EXECUTOR_START'
|
|
23
|
-
GIT_SYNC_SUBMODULES_VAR = 'GIT_SYNC_SUBMODULES'
|
|
24
|
-
|
|
25
|
-
GIT_ENABLE_GIT_INTEGRATION_VAR = 'GIT_ENABLE_GIT_INTEGRATION'
|
|
26
|
-
GIT_OVERWRITE_WITH_PROJECT_SETTINGS_VAR = 'GIT_OVERWRITE_WITH_PROJECT_SETTINGS'
|
|
27
|
-
|
|
28
27
|
ENV_VAR_TO_CONFIG_KEY = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
GIT_REPO_LINK: 'remote_repo_link',
|
|
29
|
+
GIT_REPO_PATH: 'repo_path',
|
|
30
|
+
GIT_USERNAME: 'username',
|
|
31
|
+
GIT_EMAIL: 'email',
|
|
32
|
+
GIT_AUTH_TYPE: 'auth_type',
|
|
33
|
+
GIT_BRANCH: 'branch',
|
|
34
|
+
GIT_SYNC_ON_PIPELINE_RUN: 'sync_on_pipeline_run',
|
|
35
|
+
GIT_SYNC_ON_START: 'sync_on_start',
|
|
36
|
+
GIT_SYNC_ON_EXECUTOR_START: 'sync_on_executor_start',
|
|
37
|
+
GIT_SYNC_SUBMODULES: 'sync_submodules',
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
BOOLEAN_ENV_VARS = set(
|
|
42
41
|
[
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
GIT_SYNC_ON_PIPELINE_RUN,
|
|
43
|
+
GIT_SYNC_ON_START,
|
|
44
|
+
GIT_SYNC_ON_EXECUTOR_START,
|
|
45
|
+
GIT_SYNC_SUBMODULES,
|
|
46
|
+
GIT_ENABLE_GIT_INTEGRATION,
|
|
48
47
|
]
|
|
49
48
|
)
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
def get_value_for_sync_config(env_var) -> bool:
|
|
53
|
-
value = os.getenv(env_var)
|
|
51
|
+
def get_value_for_sync_config(env_var, value) -> bool:
|
|
54
52
|
return get_bool_value(value) if env_var in BOOLEAN_ENV_VARS else value
|
|
55
53
|
|
|
56
54
|
|
|
@@ -68,21 +66,28 @@ def build_sync_config(project_sync_config: Dict) -> Dict:
|
|
|
68
66
|
if v is not None and (not isinstance(v, str) or v)
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
# Read settings from settings backend
|
|
70
|
+
config_from_settings = {
|
|
71
|
+
k: get_settings_value(k)
|
|
72
|
+
for k in ENV_VAR_TO_CONFIG_KEY.keys()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if any([v is not None for v in config_from_settings.values()]):
|
|
76
|
+
# Map sync config keys to settings values
|
|
72
77
|
sync_config = {
|
|
73
|
-
v: get_value_for_sync_config(k)
|
|
78
|
+
v: get_value_for_sync_config(k, config_from_settings.get(k))
|
|
74
79
|
for k, v in ENV_VAR_TO_CONFIG_KEY.items()
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
# If the GIT_OVERWRITE_WITH_PROJECT_SETTINGS environment variable is set to true,
|
|
78
83
|
# overwrite the sync_config with the config from the project preferences file.
|
|
79
|
-
if get_bool_value(
|
|
84
|
+
if get_bool_value(get_settings_value(GIT_OVERWRITE_WITH_PROJECT_SETTINGS)):
|
|
80
85
|
sync_config = merge_dict(sync_config, project_sync_config)
|
|
81
86
|
else:
|
|
82
87
|
sync_config = project_sync_config
|
|
83
88
|
|
|
84
89
|
# Set the enable_git_integration field from environment variables
|
|
85
|
-
enable_git_integration =
|
|
90
|
+
enable_git_integration = get_settings_value(GIT_ENABLE_GIT_INTEGRATION)
|
|
86
91
|
if enable_git_integration is not None:
|
|
87
92
|
sync_config['enable_git_integration'] = get_bool_value(enable_git_integration)
|
|
88
93
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
1
3
|
import os
|
|
2
4
|
import traceback
|
|
3
5
|
import uuid
|
|
@@ -11,6 +13,7 @@ from jinja2 import Template
|
|
|
11
13
|
|
|
12
14
|
from mage_ai.cluster_manager.constants import ClusterType
|
|
13
15
|
from mage_ai.data_preparation.templates.utils import copy_template_directory
|
|
16
|
+
from mage_ai.settings import INITIAL_METADATA, settings
|
|
14
17
|
from mage_ai.settings.repo import DEFAULT_MAGE_DATA_DIR, MAGE_DATA_DIR_ENV_VAR
|
|
15
18
|
from mage_ai.settings.repo import get_data_dir as get_data_dir_new
|
|
16
19
|
from mage_ai.settings.repo import get_metadata_path
|
|
@@ -25,6 +28,8 @@ yml.preserve_quotes = True
|
|
|
25
28
|
yml.width = float('inf')
|
|
26
29
|
yml.indent(mapping=2, sequence=2, offset=0)
|
|
27
30
|
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
28
33
|
|
|
29
34
|
class ProjectType(str, Enum):
|
|
30
35
|
MAIN = 'main'
|
|
@@ -64,9 +69,12 @@ class RepoConfig:
|
|
|
64
69
|
self.ldap_config = None
|
|
65
70
|
self.s3_bucket = None
|
|
66
71
|
self.s3_path_prefix = None
|
|
72
|
+
self.settings_backend = None
|
|
67
73
|
self.logging_config = None
|
|
68
74
|
self.variables_dir = None
|
|
69
75
|
self.variables_retention_period = None
|
|
76
|
+
self.workspace_initial_metadata = None
|
|
77
|
+
self.workspace_shared_config = None
|
|
70
78
|
|
|
71
79
|
from mage_ai.data_preparation.shared.utils import get_template_vars
|
|
72
80
|
try:
|
|
@@ -125,6 +133,7 @@ class RepoConfig:
|
|
|
125
133
|
self.pipelines = repo_config.get('pipelines')
|
|
126
134
|
self.retry_config = repo_config.get('retry_config')
|
|
127
135
|
self.workspace_config_defaults = repo_config.get('workspace_config_defaults')
|
|
136
|
+
self.workspace_initial_metadata = repo_config.get('workspace_initial_metadata')
|
|
128
137
|
|
|
129
138
|
self.ldap_config = repo_config.get('ldap_config')
|
|
130
139
|
|
|
@@ -136,6 +145,8 @@ class RepoConfig:
|
|
|
136
145
|
self.s3_bucket = path_parts.pop(0)
|
|
137
146
|
self.s3_path_prefix = '/'.join(path_parts)
|
|
138
147
|
|
|
148
|
+
self.settings_backend = repo_config.get('settings_backend', dict())
|
|
149
|
+
|
|
139
150
|
self.logging_config = repo_config.get('logging_config', dict())
|
|
140
151
|
|
|
141
152
|
self.variables_retention_period = repo_config.get('variables_retention_period')
|
|
@@ -233,6 +244,11 @@ def init_repo(
|
|
|
233
244
|
raise FileExistsError(f'Repository {repo_path} already exists')
|
|
234
245
|
|
|
235
246
|
new_config = dict()
|
|
247
|
+
if INITIAL_METADATA:
|
|
248
|
+
try:
|
|
249
|
+
new_config = json.loads(INITIAL_METADATA)
|
|
250
|
+
except Exception:
|
|
251
|
+
logger.exception('Error loading initial metadata.')
|
|
236
252
|
if project_type == ProjectType.MAIN:
|
|
237
253
|
copy_template_directory('main', repo_path)
|
|
238
254
|
new_config.update(
|
|
@@ -300,6 +316,11 @@ def set_project_uuid_from_metadata() -> None:
|
|
|
300
316
|
project_uuid = config.get('project_uuid')
|
|
301
317
|
|
|
302
318
|
|
|
319
|
+
def update_settings_on_metadata_change() -> None:
|
|
320
|
+
repo_config = get_repo_config(root_project=True)
|
|
321
|
+
settings.set_settings_backend(**repo_config.settings_backend)
|
|
322
|
+
|
|
323
|
+
|
|
303
324
|
def init_project_uuid(overwrite_uuid: str = None, root_project: bool = False) -> None:
|
|
304
325
|
"""
|
|
305
326
|
Initialize the project_uuid constant. The project_uuid constant is used throughout
|
|
@@ -29,11 +29,36 @@ class GCSStorage(BaseStorage):
|
|
|
29
29
|
path += '/'
|
|
30
30
|
return self.path_exists(path)
|
|
31
31
|
|
|
32
|
-
def listdir(
|
|
32
|
+
def listdir(
|
|
33
|
+
self,
|
|
34
|
+
path: str,
|
|
35
|
+
suffix: str = None,
|
|
36
|
+
max_results: int = None,
|
|
37
|
+
) -> List[str]:
|
|
33
38
|
if not path.endswith('/'):
|
|
34
39
|
path += '/'
|
|
35
40
|
path = gcs_url_path(path)
|
|
36
|
-
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
NOTE: The number of results returned by the GCS bucket list_blobs method may
|
|
44
|
+
be lower than the number entered for the max_results argument, since this
|
|
45
|
+
method recursively fetches files inside of subdirectories at the path
|
|
46
|
+
specified, not just the directories themselves at the top level of the path.
|
|
47
|
+
|
|
48
|
+
As a result, when fetching block output folders in a remote variables directory,
|
|
49
|
+
the json or parquet files inside of the block output folders (e.g. the "type.json"
|
|
50
|
+
file in folder "output_0") are counted towards the "max_results" limit. If max_results
|
|
51
|
+
is set to 10, less than 10 output folders may be returned due to there being multiple
|
|
52
|
+
json or other files in each output folder. We do not increase the max_results or
|
|
53
|
+
include a "delimiter" argument because it would increase load times to unacceptable levels.
|
|
54
|
+
|
|
55
|
+
Example block output path in GCS:
|
|
56
|
+
gs://mage_demo/pipelines/example_pipeline/.variables/example_block/output_0/
|
|
57
|
+
"""
|
|
58
|
+
blobs = self.bucket.list_blobs(
|
|
59
|
+
prefix=path,
|
|
60
|
+
max_results=max_results,
|
|
61
|
+
)
|
|
37
62
|
keys = []
|
|
38
63
|
for blob in blobs:
|
|
39
64
|
# Avoid finding files recursevively in the dir path.
|
|
@@ -19,12 +19,27 @@ class LocalStorage(BaseStorage):
|
|
|
19
19
|
def isdir(self, path: str) -> bool:
|
|
20
20
|
return os.path.isdir(path)
|
|
21
21
|
|
|
22
|
-
def listdir(
|
|
22
|
+
def listdir(
|
|
23
|
+
self,
|
|
24
|
+
path: str,
|
|
25
|
+
suffix: str = None,
|
|
26
|
+
max_results: int = None,
|
|
27
|
+
) -> List[str]:
|
|
28
|
+
paths = []
|
|
23
29
|
if not os.path.exists(path):
|
|
24
|
-
return
|
|
25
|
-
|
|
30
|
+
return paths
|
|
31
|
+
|
|
32
|
+
if max_results is not None:
|
|
33
|
+
with os.scandir(path) as it:
|
|
34
|
+
for idx, entry in enumerate(it):
|
|
35
|
+
paths.append(entry.name)
|
|
36
|
+
if idx >= max_results - 1:
|
|
37
|
+
break
|
|
38
|
+
else:
|
|
39
|
+
paths = os.listdir(path)
|
|
26
40
|
if suffix is not None:
|
|
27
41
|
paths = [p for p in paths if p.endswith(suffix)]
|
|
42
|
+
|
|
28
43
|
return paths
|
|
29
44
|
|
|
30
45
|
def makedirs(self, path: str, **kwargs) -> None:
|
|
@@ -30,11 +30,16 @@ class S3Storage(BaseStorage):
|
|
|
30
30
|
path += '/'
|
|
31
31
|
return self.path_exists(path)
|
|
32
32
|
|
|
33
|
-
def listdir(
|
|
33
|
+
def listdir(
|
|
34
|
+
self,
|
|
35
|
+
path: str,
|
|
36
|
+
suffix: str = None,
|
|
37
|
+
max_results: int = None,
|
|
38
|
+
) -> List[str]:
|
|
34
39
|
if not path.endswith('/'):
|
|
35
40
|
path += '/'
|
|
36
41
|
path = s3_url_path(path)
|
|
37
|
-
keys = self.client.listdir(path, suffix=suffix)
|
|
42
|
+
keys = self.client.listdir(path, suffix=suffix, max_results=max_results)
|
|
38
43
|
return [k[len(path):].rstrip('/') for k in keys]
|
|
39
44
|
|
|
40
45
|
def makedirs(self, path: str, **kwargs) -> None:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from mage_ai.streaming.sources.base_python import BasePythonSource
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
if 'streaming_source' not in globals():
|
|
5
|
+
from mage_ai.data_preparation.decorators import streaming_source
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@streaming_source
|
|
9
|
+
class CustomSource(BasePythonSource):
|
|
10
|
+
def init_client(self):
|
|
11
|
+
"""
|
|
12
|
+
Implement the logic of initializing the client.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def batch_read(self, handler: Callable):
|
|
16
|
+
"""
|
|
17
|
+
Batch read the messages from the source and use handler to process the messages.
|
|
18
|
+
"""
|
|
19
|
+
while True:
|
|
20
|
+
records = []
|
|
21
|
+
# Implement the logic of fetching the records
|
|
22
|
+
if len(records) > 0:
|
|
23
|
+
handler(records)
|
|
@@ -3,6 +3,12 @@ project_type: main
|
|
|
3
3
|
variables_dir: ~/.mage_data
|
|
4
4
|
# remote_variables_dir: s3://bucket/path_prefix
|
|
5
5
|
|
|
6
|
+
# Project metadata that will be prepopulated for workspaces created from this project.
|
|
7
|
+
workspace_initial_metadata: {}
|
|
8
|
+
|
|
9
|
+
# Default workspace configuration.
|
|
10
|
+
workspace_default_config: {}
|
|
11
|
+
|
|
6
12
|
variables_retention_period: '90d'
|
|
7
13
|
|
|
8
14
|
emr_config:
|
|
@@ -140,19 +140,23 @@ def __fetch_data_loader_templates(
|
|
|
140
140
|
pipeline_type: PipelineType = PipelineType.PYTHON,
|
|
141
141
|
) -> str:
|
|
142
142
|
data_source = config.get('data_source')
|
|
143
|
+
default_template_name = None
|
|
143
144
|
file_extension = 'py'
|
|
144
145
|
if pipeline_type == PipelineType.PYSPARK:
|
|
145
146
|
template_folder = 'data_loaders/pyspark'
|
|
146
147
|
elif pipeline_type == PipelineType.STREAMING:
|
|
147
148
|
template_folder = 'data_loaders/streaming'
|
|
148
|
-
|
|
149
|
+
if language == BlockLanguage.YAML:
|
|
150
|
+
file_extension = 'yaml'
|
|
151
|
+
else:
|
|
152
|
+
default_template_name = 'generic_python.py'
|
|
149
153
|
elif language == BlockLanguage.R:
|
|
150
154
|
template_folder = 'data_loaders/r'
|
|
151
155
|
file_extension = 'r'
|
|
152
156
|
else:
|
|
153
157
|
template_folder = 'data_loaders'
|
|
154
158
|
|
|
155
|
-
default_template = template_folder + '/' + 'default.jinja'
|
|
159
|
+
default_template = template_folder + '/' + (default_template_name or 'default.jinja')
|
|
156
160
|
if data_source is None:
|
|
157
161
|
template_path = default_template
|
|
158
162
|
else:
|
|
@@ -271,6 +271,7 @@ class VariableManager:
|
|
|
271
271
|
block_uuid: str,
|
|
272
272
|
partition: str = None,
|
|
273
273
|
clean_block_uuid: bool = True,
|
|
274
|
+
max_results: int = None,
|
|
274
275
|
) -> List[str]:
|
|
275
276
|
variable_dir_path = os.path.join(
|
|
276
277
|
self.pipeline_path(pipeline_uuid),
|
|
@@ -280,7 +281,7 @@ class VariableManager:
|
|
|
280
281
|
)
|
|
281
282
|
if not self.storage.path_exists(variable_dir_path):
|
|
282
283
|
return []
|
|
283
|
-
variables = self.storage.listdir(variable_dir_path)
|
|
284
|
+
variables = self.storage.listdir(variable_dir_path, max_results=max_results)
|
|
284
285
|
return sorted([v.split('.')[0] for v in variables])
|
|
285
286
|
|
|
286
287
|
def pipeline_path(self, pipeline_uuid: str) -> str:
|
mage_ai/io/base.py
CHANGED
|
@@ -302,8 +302,11 @@ class BaseSQLDatabase(BaseIO):
|
|
|
302
302
|
self,
|
|
303
303
|
column_name: str,
|
|
304
304
|
allow_reserved_words: bool = False,
|
|
305
|
+
auto_clean_name: bool = True,
|
|
305
306
|
case_sensitive: bool = False,
|
|
306
307
|
) -> str:
|
|
308
|
+
if not auto_clean_name:
|
|
309
|
+
return False
|
|
307
310
|
col_new = clean_name(column_name, case_sensitive=case_sensitive)
|
|
308
311
|
if not allow_reserved_words and col_new.upper() in SQL_RESERVED_WORDS:
|
|
309
312
|
col_new = f'_{col_new}'
|
mage_ai/io/bigquery.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Dict, List, Mapping, Union
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
3
4
|
from google.cloud.bigquery import (
|
|
4
5
|
Client,
|
|
5
6
|
LoadJobConfig,
|
|
@@ -295,6 +296,7 @@ WHERE table_id = '{table_name}'
|
|
|
295
296
|
column_types = self.get_column_types(schema, table_name)
|
|
296
297
|
|
|
297
298
|
if df is not None:
|
|
299
|
+
df.fillna(value=np.NaN, inplace=True)
|
|
298
300
|
for col in df.columns:
|
|
299
301
|
col_type = column_types.get(col)
|
|
300
302
|
if not col_type:
|