mage-ai 0.8.47__py3-none-any.whl → 0.8.49__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/resources/DataProviderResource.py +13 -1
- mage_ai/data_preparation/git/__init__.py +1 -1
- mage_ai/data_preparation/models/block/__init__.py +5 -0
- mage_ai/data_preparation/models/block/sql/__init__.py +68 -8
- mage_ai/data_preparation/models/block/sql/clickhouse.py +38 -0
- mage_ai/data_preparation/models/block/utils.py +4 -2
- mage_ai/data_preparation/models/file.py +4 -3
- mage_ai/data_preparation/models/pipeline.py +36 -3
- mage_ai/data_preparation/templates/repo/io_config.yaml +12 -0
- mage_ai/io/base.py +1 -0
- mage_ai/io/clickhouse.py +237 -0
- mage_ai/io/config.py +19 -0
- mage_ai/io/io_config.py +1 -0
- mage_ai/io/snowflake.py +0 -1
- mage_ai/io/sql.py +4 -1
- mage_ai/server/constants.py +1 -1
- mage_ai/server/frontend_dist/404.html +2 -2
- mage_ai/server/frontend_dist/404.html.html +2 -2
- mage_ai/server/frontend_dist/_next/static/I2bRLK9B7Lap2LvCicTKv/_buildManifest.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{2249-50d2156ae3ce5933.js → 2249-59feb0a0585ef7c1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/2407-35c362852abff411.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/2524-ecbe3dd70d06cbe4.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{3014-26ba07eb6a78e31c.js → 3014-a01e16bc067500ad.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{4506-ce5fa63b65f6fa5f.js → 4506-a3c53b0033972626.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/4741-5fbecd08232ba3f9.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{5477-793cd2120261d023.js → 5477-b439f211b6146a11.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/5716-c5952e5ca9c8e139.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/{5872-103815a4a043489b.js → 5872-1767c45ee6690ae5.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{5896-f84e336fb8877027.js → 5896-10a676bcc86978cc.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{6166-5fc2768ef661e7da.js → 6166-97cb3cc7d00d645c.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{6532-b0fd357eb359c127.js → 6532-5f86e9e1f32adf3a.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{7400-acab00d6265bd4dc.js → 7400-033fb12e7c46f62e.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/{9832-c8b8970bb522f302.js → 9832-f97919376d52e3bf.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{manage-96c89b2a2689f80c.js → manage-8a7b2b707c7038fb.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipeline-runs-294eccc0163c80a1.js → pipeline-runs-832218136947bf56.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-c48a45cea6df6192.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-dd637dcb042ff771.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-5541c3e13297ddbc.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{logs-0e6935a2b7f8e1ed.js → logs-caa9fade2bc783b9.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runs-0e02bcdfcd533830.js → block-runs-1b44f28966826dbe.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runtime-baf8bb6da01976db.js → block-runtime-44029312dc580504.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{monitors-d42158676a75b451.js → monitors-9b46466e066d6372.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/{[run]-4049561456ea2314.js → [run]-5f78885afc7758df.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{runs-bebeee2854c734fc.js → runs-43f2c1658cd3d03d.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{settings-b95afd173fb265df.js → settings-8fb53eb8dd0267e1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{syncs-e46a9b03e0fff91a.js → syncs-c92351b1a81f7a13.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/{[...slug]-6a95567766a1d00e.js → [...slug]-ca76f5938bdff8a7.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-c259bd9d385404fa.js → triggers-4e9518882c8730b1.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipelines-0900be641ce4e6ec.js → pipelines-c46347f0308ab09a.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{preferences-25735fdf80f95015.js → preferences-45c40fb24c8a5139.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{sync-data-abfaa2b15eef72a0.js → sync-data-6e80afa0c9b90cdf.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-5d0c51213b9bba8e.js → users-12a90f932ff4fc9d.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{terminal-24eb5df948cc9a7e.js → terminal-cc54b4d4e5295101.js} +1 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/test-bd801bde63db7c9e.js +1 -0
- mage_ai/server/frontend_dist/_next/static/chunks/pages/{triggers-6ff85ea842bde8c2.js → triggers-08774e473ea4a96b.js} +1 -1
- mage_ai/server/frontend_dist/index.html +2 -2
- mage_ai/server/frontend_dist/manage.html +2 -2
- mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
- mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
- mage_ai/server/frontend_dist/pipelines.html +2 -2
- mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
- mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
- mage_ai/server/frontend_dist/settings.html +2 -2
- mage_ai/server/frontend_dist/sign-in.html +2 -2
- mage_ai/server/frontend_dist/terminal.html +2 -2
- mage_ai/server/frontend_dist/test.html +3 -3
- mage_ai/server/frontend_dist/triggers.html +2 -2
- mage_ai/server/utils/output_display.py +1 -3
- mage_ai/server/websocket_server.py +0 -1
- {mage_ai-0.8.47.dist-info → mage_ai-0.8.49.dist-info}/METADATA +4 -1
- {mage_ai-0.8.47.dist-info → mage_ai-0.8.49.dist-info}/RECORD +92 -90
- mage_ai/server/frontend_dist/_next/static/chunks/2083-78a438dbdc57d1e4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/2524-6aeb9419acf5d1b4.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/4538-8a3c3e47be976ede.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/4741-acfe40cf3b06659e.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-e512333242e02c60.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-774dd41941497640.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-c6179a9e737009c9.js +0 -1
- mage_ai/server/frontend_dist/_next/static/chunks/pages/test-85cf18ae78ff535c.js +0 -1
- mage_ai/server/frontend_dist/_next/static/xTScbs8HLliQCDHzVPx5E/_buildManifest.js +0 -1
- /mage_ai/server/frontend_dist/_next/static/{xTScbs8HLliQCDHzVPx5E → I2bRLK9B7Lap2LvCicTKv}/_middlewareManifest.js +0 -0
- /mage_ai/server/frontend_dist/_next/static/{xTScbs8HLliQCDHzVPx5E → I2bRLK9B7Lap2LvCicTKv}/_ssgManifest.js +0 -0
- {mage_ai-0.8.47.dist-info → mage_ai-0.8.49.dist-info}/LICENSE +0 -0
- {mage_ai-0.8.47.dist-info → mage_ai-0.8.49.dist-info}/WHEEL +0 -0
- {mage_ai-0.8.47.dist-info → mage_ai-0.8.49.dist-info}/entry_points.txt +0 -0
- {mage_ai-0.8.47.dist-info → mage_ai-0.8.49.dist-info}/top_level.txt +0 -0
|
@@ -8,12 +8,24 @@ import yaml
|
|
|
8
8
|
|
|
9
9
|
DATA_PROVIDERS = [
|
|
10
10
|
DataSource.BIGQUERY,
|
|
11
|
+
DataSource.CLICKHOUSE,
|
|
11
12
|
DataSource.MSSQL,
|
|
12
13
|
DataSource.MYSQL,
|
|
13
14
|
DataSource.POSTGRES,
|
|
14
15
|
DataSource.REDSHIFT,
|
|
15
16
|
DataSource.SNOWFLAKE,
|
|
17
|
+
DataSource.TRINO,
|
|
16
18
|
]
|
|
19
|
+
DATA_PROVIDERS_NAME = {
|
|
20
|
+
DataSource.BIGQUERY: 'BigQuery',
|
|
21
|
+
DataSource.CLICKHOUSE: 'ClickHouse',
|
|
22
|
+
DataSource.MSSQL: 'Microsoft SQL Server',
|
|
23
|
+
DataSource.MYSQL: 'MySQL',
|
|
24
|
+
DataSource.POSTGRES: 'PostgreSQL',
|
|
25
|
+
DataSource.REDSHIFT: 'Redshift',
|
|
26
|
+
DataSource.SNOWFLAKE: 'Snowflake',
|
|
27
|
+
DataSource.TRINO: 'Trino',
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
|
|
19
31
|
class DataProviderResource(GenericResource):
|
|
@@ -27,7 +39,7 @@ class DataProviderResource(GenericResource):
|
|
|
27
39
|
print(exc)
|
|
28
40
|
|
|
29
41
|
collection = [dict(
|
|
30
|
-
id=ds.
|
|
42
|
+
id=DATA_PROVIDERS_NAME[ds.value],
|
|
31
43
|
profiles=[p for p in profiles if p != 'version'],
|
|
32
44
|
value=ds.value,
|
|
33
45
|
) for ds in DATA_PROVIDERS]
|
|
@@ -5,7 +5,6 @@ from mage_ai.orchestration.db.models.oauth import User
|
|
|
5
5
|
from urllib.parse import urlparse
|
|
6
6
|
import asyncio
|
|
7
7
|
import base64
|
|
8
|
-
import git
|
|
9
8
|
import os
|
|
10
9
|
import subprocess
|
|
11
10
|
|
|
@@ -15,6 +14,7 @@ REMOTE_NAME = 'mage-repo'
|
|
|
15
14
|
|
|
16
15
|
class Git:
|
|
17
16
|
def __init__(self, git_config: GitConfig):
|
|
17
|
+
import git
|
|
18
18
|
self.remote_repo_link = git_config.remote_repo_link
|
|
19
19
|
self.repo_path = git_config.repo_path or os.getcwd()
|
|
20
20
|
os.makedirs(self.repo_path, exist_ok=True)
|
|
@@ -655,6 +655,7 @@ class Block:
|
|
|
655
655
|
dynamic_block_uuid: str = None,
|
|
656
656
|
dynamic_upstream_block_uuids: List[str] = None,
|
|
657
657
|
run_settings: Dict = None,
|
|
658
|
+
**kwargs,
|
|
658
659
|
) -> Dict:
|
|
659
660
|
try:
|
|
660
661
|
if not run_all_blocks:
|
|
@@ -685,6 +686,7 @@ class Block:
|
|
|
685
686
|
dynamic_block_index=dynamic_block_index,
|
|
686
687
|
dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
|
|
687
688
|
run_settings=run_settings,
|
|
689
|
+
**kwargs,
|
|
688
690
|
)
|
|
689
691
|
block_output = self.post_process_output(output)
|
|
690
692
|
variable_mapping = dict()
|
|
@@ -867,6 +869,7 @@ class Block:
|
|
|
867
869
|
dynamic_block_index: int = None,
|
|
868
870
|
dynamic_upstream_block_uuids: List[str] = None,
|
|
869
871
|
run_settings: Dict = None,
|
|
872
|
+
**kwargs,
|
|
870
873
|
) -> Dict:
|
|
871
874
|
# Add pipeline uuid and block uuid to global_vars
|
|
872
875
|
global_vars = merge_dict(
|
|
@@ -922,6 +925,7 @@ class Block:
|
|
|
922
925
|
runtime_arguments=runtime_arguments,
|
|
923
926
|
upstream_block_uuids=upstream_block_uuids,
|
|
924
927
|
run_settings=run_settings,
|
|
928
|
+
**kwargs,
|
|
925
929
|
)
|
|
926
930
|
|
|
927
931
|
output_message = dict(output=outputs)
|
|
@@ -942,6 +946,7 @@ class Block:
|
|
|
942
946
|
runtime_arguments: Dict = None,
|
|
943
947
|
upstream_block_uuids: List[str] = None,
|
|
944
948
|
run_settings: Dict = None,
|
|
949
|
+
**kwargs,
|
|
945
950
|
) -> List:
|
|
946
951
|
decorated_functions = []
|
|
947
952
|
test_functions = []
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from mage_ai.data_preparation.models.block import Block
|
|
2
2
|
from mage_ai.data_preparation.models.block.sql import (
|
|
3
3
|
bigquery,
|
|
4
|
+
clickhouse,
|
|
4
5
|
mssql,
|
|
5
6
|
mysql,
|
|
6
7
|
postgres,
|
|
@@ -14,8 +15,12 @@ from mage_ai.data_preparation.models.block.sql.utils.shared import (
|
|
|
14
15
|
)
|
|
15
16
|
from mage_ai.data_preparation.models.constants import BlockType
|
|
16
17
|
from mage_ai.data_preparation.repo_manager import get_repo_path
|
|
17
|
-
from mage_ai.io.base import
|
|
18
|
-
|
|
18
|
+
from mage_ai.io.base import (
|
|
19
|
+
DataSource,
|
|
20
|
+
ExportWritePolicy,
|
|
21
|
+
QUERY_ROW_LIMIT,
|
|
22
|
+
)
|
|
23
|
+
from mage_ai.io.config import ConfigFileLoader, ConfigKey
|
|
19
24
|
from os import path
|
|
20
25
|
from time import sleep
|
|
21
26
|
from typing import Any, Dict, List
|
|
@@ -37,6 +42,7 @@ def execute_sql_code(
|
|
|
37
42
|
global_vars: Dict = None,
|
|
38
43
|
config_file_loader: Any = None,
|
|
39
44
|
configuration: Dict = None,
|
|
45
|
+
test_execution: bool = False,
|
|
40
46
|
) -> List[Any]:
|
|
41
47
|
configuration = configuration if configuration else block.configuration
|
|
42
48
|
use_raw_sql = configuration.get('use_raw_sql')
|
|
@@ -66,6 +72,12 @@ def execute_sql_code(
|
|
|
66
72
|
verbose=BlockType.DATA_EXPORTER == block.type,
|
|
67
73
|
)
|
|
68
74
|
|
|
75
|
+
limit = int(configuration.get('limit') or QUERY_ROW_LIMIT)
|
|
76
|
+
if test_execution:
|
|
77
|
+
limit = min(limit, QUERY_ROW_LIMIT)
|
|
78
|
+
else:
|
|
79
|
+
limit = QUERY_ROW_LIMIT
|
|
80
|
+
|
|
69
81
|
if DataSource.BIGQUERY.value == data_provider:
|
|
70
82
|
from mage_ai.io.bigquery import BigQuery
|
|
71
83
|
|
|
@@ -111,12 +123,54 @@ def execute_sql_code(
|
|
|
111
123
|
try:
|
|
112
124
|
result = loader.load(
|
|
113
125
|
f'SELECT * FROM {database}.{schema}.{table_name}',
|
|
126
|
+
limit=limit,
|
|
114
127
|
verbose=False,
|
|
115
128
|
)
|
|
116
129
|
return [result]
|
|
117
130
|
except Exception as err:
|
|
118
131
|
if '404' not in str(err):
|
|
119
132
|
raise err
|
|
133
|
+
elif DataSource.CLICKHOUSE.value == data_provider:
|
|
134
|
+
from mage_ai.io.clickhouse import ClickHouse
|
|
135
|
+
|
|
136
|
+
loader = ClickHouse.with_config(config_file_loader)
|
|
137
|
+
clickhouse.create_upstream_block_tables(
|
|
138
|
+
loader,
|
|
139
|
+
block,
|
|
140
|
+
configuration=configuration,
|
|
141
|
+
execution_partition=execution_partition,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
query_string = clickhouse.interpolate_input_data(block, query)
|
|
145
|
+
query_string = interpolate_vars(
|
|
146
|
+
query_string, global_vars=global_vars)
|
|
147
|
+
|
|
148
|
+
database = database or 'default'
|
|
149
|
+
|
|
150
|
+
if use_raw_sql:
|
|
151
|
+
return execute_raw_sql(
|
|
152
|
+
loader,
|
|
153
|
+
block,
|
|
154
|
+
query_string,
|
|
155
|
+
configuration=configuration,
|
|
156
|
+
should_query=should_query,
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
loader.export(
|
|
160
|
+
None,
|
|
161
|
+
table_name=table_name,
|
|
162
|
+
database=database,
|
|
163
|
+
query_string=query_string,
|
|
164
|
+
**kwargs_shared,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if should_query:
|
|
168
|
+
return [
|
|
169
|
+
loader.load(
|
|
170
|
+
f'SELECT * FROM {database}.{table_name}',
|
|
171
|
+
verbose=False,
|
|
172
|
+
),
|
|
173
|
+
]
|
|
120
174
|
elif DataSource.MSSQL.value == data_provider:
|
|
121
175
|
from mage_ai.io.mssql import MSSQL
|
|
122
176
|
|
|
@@ -155,6 +209,7 @@ def execute_sql_code(
|
|
|
155
209
|
return [
|
|
156
210
|
loader.load(
|
|
157
211
|
f'SELECT * FROM {table_name}',
|
|
212
|
+
limit=limit,
|
|
158
213
|
verbose=False,
|
|
159
214
|
),
|
|
160
215
|
]
|
|
@@ -193,6 +248,7 @@ def execute_sql_code(
|
|
|
193
248
|
return [
|
|
194
249
|
loader.load(
|
|
195
250
|
f'SELECT * FROM {table_name}',
|
|
251
|
+
limit=limit,
|
|
196
252
|
verbose=False,
|
|
197
253
|
),
|
|
198
254
|
]
|
|
@@ -231,6 +287,7 @@ def execute_sql_code(
|
|
|
231
287
|
return [
|
|
232
288
|
loader.load(
|
|
233
289
|
f'SELECT * FROM {schema}.{table_name}',
|
|
290
|
+
limit=limit,
|
|
234
291
|
verbose=False,
|
|
235
292
|
),
|
|
236
293
|
]
|
|
@@ -269,6 +326,7 @@ def execute_sql_code(
|
|
|
269
326
|
return [
|
|
270
327
|
loader.load(
|
|
271
328
|
f'SELECT * FROM {schema}.{table_name}',
|
|
329
|
+
limit=limit,
|
|
272
330
|
verbose=False,
|
|
273
331
|
),
|
|
274
332
|
]
|
|
@@ -316,6 +374,7 @@ def execute_sql_code(
|
|
|
316
374
|
database=database,
|
|
317
375
|
schema=schema,
|
|
318
376
|
table_name=table_name,
|
|
377
|
+
limit=limit,
|
|
319
378
|
verbose=False,
|
|
320
379
|
),
|
|
321
380
|
]
|
|
@@ -344,21 +403,20 @@ def execute_sql_code(
|
|
|
344
403
|
else:
|
|
345
404
|
loader.export(
|
|
346
405
|
None,
|
|
347
|
-
table_name,
|
|
348
|
-
database,
|
|
349
406
|
schema,
|
|
407
|
+
table_name,
|
|
350
408
|
if_exists=export_write_policy,
|
|
351
409
|
query_string=query_string,
|
|
352
410
|
verbose=BlockType.DATA_EXPORTER == block.type,
|
|
353
411
|
)
|
|
354
412
|
|
|
355
413
|
if should_query:
|
|
414
|
+
catalog = config_file_loader[ConfigKey.TRINO_CATALOG]
|
|
415
|
+
|
|
356
416
|
return [
|
|
357
417
|
loader.load(
|
|
358
|
-
f'SELECT * FROM "{
|
|
359
|
-
|
|
360
|
-
schema=schema,
|
|
361
|
-
table_name=table_name,
|
|
418
|
+
f'SELECT * FROM "{catalog}"."{schema}"."{table_name}"',
|
|
419
|
+
limit=limit,
|
|
362
420
|
verbose=False,
|
|
363
421
|
),
|
|
364
422
|
]
|
|
@@ -449,9 +507,11 @@ class SQLBlock(Block):
|
|
|
449
507
|
global_vars=None,
|
|
450
508
|
**kwargs,
|
|
451
509
|
) -> List:
|
|
510
|
+
test_execution = kwargs.get('test_execution')
|
|
452
511
|
return execute_sql_code(
|
|
453
512
|
self,
|
|
454
513
|
custom_code or self.content,
|
|
455
514
|
execution_partition=execution_partition,
|
|
456
515
|
global_vars=global_vars,
|
|
516
|
+
test_execution=test_execution,
|
|
457
517
|
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from mage_ai.data_preparation.models.block.sql.utils.shared import (
|
|
2
|
+
create_upstream_block_tables as create_upstream_block_tables_orig,
|
|
3
|
+
interpolate_input,
|
|
4
|
+
)
|
|
5
|
+
from mage_ai.io.config import ConfigKey
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_upstream_block_tables(
|
|
10
|
+
loader,
|
|
11
|
+
block,
|
|
12
|
+
cascade_on_drop: bool = False,
|
|
13
|
+
configuration: Dict = None,
|
|
14
|
+
execution_partition: str = None,
|
|
15
|
+
cache_upstream_dbt_models: bool = False,
|
|
16
|
+
):
|
|
17
|
+
create_upstream_block_tables_orig(
|
|
18
|
+
loader,
|
|
19
|
+
block,
|
|
20
|
+
cascade_on_drop,
|
|
21
|
+
configuration,
|
|
22
|
+
execution_partition,
|
|
23
|
+
cache_upstream_dbt_models,
|
|
24
|
+
cache_keys=[
|
|
25
|
+
ConfigKey.CLICKHOUSE_DATABASE,
|
|
26
|
+
ConfigKey.CLICKHOUSE_HOST,
|
|
27
|
+
ConfigKey.CLICKHOUSE_PORT,
|
|
28
|
+
],
|
|
29
|
+
no_schema=True,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def interpolate_input_data(block, query):
|
|
34
|
+
return interpolate_input(
|
|
35
|
+
block,
|
|
36
|
+
query,
|
|
37
|
+
lambda db, schema, tn: tn,
|
|
38
|
+
)
|
|
@@ -163,8 +163,10 @@ def create_block_runs_from_dynamic_block(
|
|
|
163
163
|
skip_creating_downstream = True
|
|
164
164
|
break
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
down_uuids_as_ancestors = []
|
|
167
|
+
for down in dynamic_ancestor.downstream_blocks:
|
|
168
|
+
if down.uuid in ancestors_uuids and not should_reduce_output(down):
|
|
169
|
+
down_uuids_as_ancestors.append(down.uuid)
|
|
168
170
|
skip_creating_downstream = len(down_uuids_as_ancestors) >= 2
|
|
169
171
|
|
|
170
172
|
# Only create downstream block runs if it doesn’t have dynamically created upstream
|
|
@@ -20,7 +20,7 @@ INACCESSIBLE_DIRS = frozenset(['__pycache__'])
|
|
|
20
20
|
MAX_DEPTH = 30
|
|
21
21
|
MAX_NUMBER_OF_FILE_VERSIONS = int(os.getenv('MAX_NUMBER_OF_FILE_VERSIONS', 100) or 100)
|
|
22
22
|
|
|
23
|
-
PIPELINES_FOLDER_PREFIX = 'pipelines
|
|
23
|
+
PIPELINES_FOLDER_PREFIX = f'pipelines{os.sep}'
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class File:
|
|
@@ -118,8 +118,9 @@ class File:
|
|
|
118
118
|
filename: str,
|
|
119
119
|
) -> Tuple[str, str]:
|
|
120
120
|
return os.path.join(
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
repo_path,
|
|
122
|
+
FILE_VERSIONS_DIR,
|
|
123
|
+
dir_path.replace(repo_path, os.path.join(repo_path, FILE_VERSIONS_DIR)),
|
|
123
124
|
filename,
|
|
124
125
|
)
|
|
125
126
|
|
|
@@ -21,7 +21,7 @@ from mage_ai.data_preparation.templates.utils import copy_template_directory
|
|
|
21
21
|
from mage_ai.data_preparation.variable_manager import VariableManager
|
|
22
22
|
from mage_ai.orchestration.db import db_connection, safe_db_query
|
|
23
23
|
from mage_ai.shared.array import find
|
|
24
|
-
from mage_ai.shared.hash import extract, ignore_keys, merge_dict
|
|
24
|
+
from mage_ai.shared.hash import extract, index_by, ignore_keys, merge_dict
|
|
25
25
|
from mage_ai.shared.io import safe_write, safe_write_async
|
|
26
26
|
from mage_ai.shared.strings import format_enum
|
|
27
27
|
from mage_ai.shared.utils import clean_name
|
|
@@ -674,6 +674,12 @@ class Pipeline:
|
|
|
674
674
|
)
|
|
675
675
|
should_save = True
|
|
676
676
|
|
|
677
|
+
blocks = data.get('blocks', [])
|
|
678
|
+
|
|
679
|
+
if blocks:
|
|
680
|
+
if not should_save and self.__update_block_order(blocks):
|
|
681
|
+
should_save = True
|
|
682
|
+
|
|
677
683
|
if should_save:
|
|
678
684
|
self.save()
|
|
679
685
|
|
|
@@ -682,8 +688,8 @@ class Pipeline:
|
|
|
682
688
|
|
|
683
689
|
arr = []
|
|
684
690
|
|
|
685
|
-
if
|
|
686
|
-
arr.append(('blocks',
|
|
691
|
+
if blocks:
|
|
692
|
+
arr.append(('blocks', blocks, self.blocks_by_uuid))
|
|
687
693
|
if 'widgets' in data:
|
|
688
694
|
arr.append(('widgets', data['widgets'], self.widgets_by_uuid))
|
|
689
695
|
|
|
@@ -778,6 +784,33 @@ class Pipeline:
|
|
|
778
784
|
if should_save_async:
|
|
779
785
|
await self.save_async(widget=widget)
|
|
780
786
|
|
|
787
|
+
def __update_block_order(self, blocks: List[Dict]) -> bool:
|
|
788
|
+
uuids_new = [b['uuid'] for b in blocks]
|
|
789
|
+
uuids_old = [b['uuid'] for b in self.block_configs]
|
|
790
|
+
|
|
791
|
+
min_length = min(len(uuids_new), len(uuids_old))
|
|
792
|
+
|
|
793
|
+
# If there are no blocks or the order has changed
|
|
794
|
+
if min_length == 0 or uuids_new[:min_length] == uuids_old[:min_length]:
|
|
795
|
+
return False
|
|
796
|
+
|
|
797
|
+
block_configs_by_uuids = index_by(lambda x: x['uuid'], self.block_configs)
|
|
798
|
+
|
|
799
|
+
block_configs = []
|
|
800
|
+
blocks_by_uuid = {}
|
|
801
|
+
|
|
802
|
+
for block_uuid in uuids_new:
|
|
803
|
+
if block_uuid in block_configs_by_uuids:
|
|
804
|
+
block_configs.append(block_configs_by_uuids[block_uuid])
|
|
805
|
+
|
|
806
|
+
if block_uuid in self.blocks_by_uuid:
|
|
807
|
+
blocks_by_uuid[block_uuid] = self.blocks_by_uuid[block_uuid]
|
|
808
|
+
|
|
809
|
+
self.block_configs = block_configs
|
|
810
|
+
self.blocks_by_uuid = blocks_by_uuid
|
|
811
|
+
|
|
812
|
+
return True
|
|
813
|
+
|
|
781
814
|
def __add_block_to_mapping(
|
|
782
815
|
self,
|
|
783
816
|
blocks_by_uuid,
|
|
@@ -10,6 +10,12 @@ default:
|
|
|
10
10
|
AZURE_CLIENT_SECRET: "{{ env_var('AZURE_CLIENT_SECRET') }}"
|
|
11
11
|
AZURE_STORAGE_ACCOUNT_NAME: "{{ env_var('AZURE_STORAGE_ACCOUNT_NAME') }}"
|
|
12
12
|
AZURE_TENANT_ID: "{{ env_var('AZURE_TENANT_ID') }}"
|
|
13
|
+
CLICKHOUSE_DATABASE: default
|
|
14
|
+
CLICKHOUSE_HOST: host.docker.internal
|
|
15
|
+
CLICKHOUSE_INTERFACE: http
|
|
16
|
+
CLICKHOUSE_PASSWORD: null
|
|
17
|
+
CLICKHOUSE_PORT: 8123
|
|
18
|
+
CLICKHOUSE_USERNAME: null
|
|
13
19
|
GOOGLE_SERVICE_ACC_KEY:
|
|
14
20
|
type: service_account
|
|
15
21
|
project_id: project-id
|
|
@@ -46,3 +52,9 @@ default:
|
|
|
46
52
|
SNOWFLAKE_DEFAULT_DB: optional_default_database
|
|
47
53
|
SNOWFLAKE_DEFAULT_SCHEMA: optional_default_schema
|
|
48
54
|
SNOWFLAKE_ROLE: role
|
|
55
|
+
TRINO_CATALOG: catalog
|
|
56
|
+
TRINO_HOST: '127.0.0.1'
|
|
57
|
+
TRINO_PASSWORD: pasword
|
|
58
|
+
TRINO_PORT: 8080
|
|
59
|
+
TRINO_SCHEMA: schema
|
|
60
|
+
TRINO_USER: user
|
mage_ai/io/base.py
CHANGED
mage_ai/io/clickhouse.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from mage_ai.io.base import BaseSQLDatabase, ExportWritePolicy, QUERY_ROW_LIMIT
|
|
2
|
+
from mage_ai.io.config import BaseConfigLoader, ConfigKey
|
|
3
|
+
from mage_ai.io.export_utils import (
|
|
4
|
+
clean_df_for_export,
|
|
5
|
+
infer_dtypes,
|
|
6
|
+
)
|
|
7
|
+
from pandas import DataFrame
|
|
8
|
+
from typing import Dict, List, Union
|
|
9
|
+
import clickhouse_connect
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ClickHouse(BaseSQLDatabase):
|
|
13
|
+
"""
|
|
14
|
+
Handles data transfer betwee a ClickHouse data warehouse and the Mage app.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, **kwargs) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Initializes settings for connecting to a ClickHouse warehouse.
|
|
19
|
+
|
|
20
|
+
To authenticate (and authorize) access to a ClickHouse warehouse,
|
|
21
|
+
credentials, i.e., username and password, must be provided.
|
|
22
|
+
|
|
23
|
+
All keyword arguments will be passed to the ClickHouse client.
|
|
24
|
+
"""
|
|
25
|
+
if kwargs.get('verbose') is not None:
|
|
26
|
+
kwargs.pop('verbose')
|
|
27
|
+
super().__init__(verbose=kwargs.get('verbose', True))
|
|
28
|
+
with self.printer.print_msg('Connecting to ClickHouse'):
|
|
29
|
+
self.client = clickhouse_connect.get_client(**kwargs)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def with_config(cls, config: BaseConfigLoader) -> 'ClickHouse':
|
|
33
|
+
"""
|
|
34
|
+
Initializes ClickHouse client from configuration loader
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
config (BaseConfigLoader): Configuration loader object
|
|
38
|
+
"""
|
|
39
|
+
if ConfigKey.CLICKHOUSE_HOST not in config:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
'No valid configuration settings found for ClickHouse. '
|
|
42
|
+
'You must specify host.'
|
|
43
|
+
)
|
|
44
|
+
return cls(
|
|
45
|
+
database=config[ConfigKey.CLICKHOUSE_DATABASE],
|
|
46
|
+
host=config[ConfigKey.CLICKHOUSE_HOST],
|
|
47
|
+
interface=config[ConfigKey.CLICKHOUSE_INTERFACE],
|
|
48
|
+
password=config[ConfigKey.CLICKHOUSE_PASSWORD],
|
|
49
|
+
port=config[ConfigKey.CLICKHOUSE_PORT],
|
|
50
|
+
username=config[ConfigKey.CLICKHOUSE_USERNAME],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def execute(self, command_string: str, **kwargs) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Sends command to the connected ClickHouse warehouse.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
command_string (str): Command to execute on the ClickHouse warehouse.
|
|
59
|
+
**kwargs: Additional arguments to pass to command, such as configurations
|
|
60
|
+
"""
|
|
61
|
+
with self.printer.print_msg(f'Executing query \'{command_string}\''):
|
|
62
|
+
command_string = self._clean_query(command_string)
|
|
63
|
+
self.client.command(command_string, **kwargs)
|
|
64
|
+
|
|
65
|
+
def execute_query(
|
|
66
|
+
self,
|
|
67
|
+
query: str,
|
|
68
|
+
parameters: Dict = None,
|
|
69
|
+
**kwargs,
|
|
70
|
+
) -> DataFrame:
|
|
71
|
+
"""
|
|
72
|
+
Sends query to the connected ClickHouse warehouse.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
query (str): Query to execute on the ClickHouse warehouse.
|
|
76
|
+
**kwargs: Additional arguments to pass to query, such as query configurations
|
|
77
|
+
"""
|
|
78
|
+
query = self._clean_query(query)
|
|
79
|
+
with self.printer.print_msg(f'Executing query \'{query}\''):
|
|
80
|
+
result = self.client.query_df(query, parameters=parameters)
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
def execute_queries(
|
|
85
|
+
self,
|
|
86
|
+
queries: List[str],
|
|
87
|
+
query_variables: List[Dict] = None,
|
|
88
|
+
fetch_query_at_indexes: List[bool] = None,
|
|
89
|
+
**kwargs,
|
|
90
|
+
) -> List:
|
|
91
|
+
results = []
|
|
92
|
+
|
|
93
|
+
for idx, query in enumerate(queries):
|
|
94
|
+
parameters = query_variables[idx] \
|
|
95
|
+
if query_variables and idx < len(query_variables) \
|
|
96
|
+
else {}
|
|
97
|
+
query = self._clean_query(query)
|
|
98
|
+
|
|
99
|
+
if fetch_query_at_indexes and idx < len(fetch_query_at_indexes) and \
|
|
100
|
+
fetch_query_at_indexes[idx]:
|
|
101
|
+
result = self.client.query_df(query, parameters=parameters)
|
|
102
|
+
else:
|
|
103
|
+
result = self.client.command(query, parameters=parameters)
|
|
104
|
+
|
|
105
|
+
results.append(result)
|
|
106
|
+
|
|
107
|
+
return results
|
|
108
|
+
|
|
109
|
+
def load(
|
|
110
|
+
self,
|
|
111
|
+
query_string: str,
|
|
112
|
+
limit: int = QUERY_ROW_LIMIT,
|
|
113
|
+
display_query: Union[str, None] = None,
|
|
114
|
+
verbose: bool = True,
|
|
115
|
+
**kwargs,
|
|
116
|
+
) -> DataFrame:
|
|
117
|
+
"""
|
|
118
|
+
Loads data from ClickHouse into a Pandas data frame based on the query given.
|
|
119
|
+
This will fail if the query returns no data from the database. When a select query
|
|
120
|
+
is provided, this function will load at maximum 10,000,000 rows of data. To operate on more
|
|
121
|
+
data, consider performing data transformations in warehouse.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
query_string (str): Query to fetch a table or subset of a table.
|
|
125
|
+
limit (int, Optional): The number of rows to limit the loaded dataframe to. Defaults to
|
|
126
|
+
10,000,000.
|
|
127
|
+
**kwargs: Additional arguments to pass to query, such as query configurations
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
DataFrame: Data frame associated with the given query.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
print_message = 'Loading data'
|
|
134
|
+
if verbose:
|
|
135
|
+
print_message += ' with query'
|
|
136
|
+
|
|
137
|
+
if display_query:
|
|
138
|
+
for line in display_query.split('\n'):
|
|
139
|
+
print_message += f'\n{line}'
|
|
140
|
+
else:
|
|
141
|
+
print_message += f'\n{query_string}'
|
|
142
|
+
|
|
143
|
+
query_string = self._clean_query(query_string)
|
|
144
|
+
|
|
145
|
+
with self.printer.print_msg(print_message):
|
|
146
|
+
return self.client.query_df(
|
|
147
|
+
self._enforce_limit(query_string, limit), **kwargs
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def export(
|
|
151
|
+
self,
|
|
152
|
+
df: DataFrame,
|
|
153
|
+
table_name: str,
|
|
154
|
+
database: str = 'default',
|
|
155
|
+
if_exists: str = 'append',
|
|
156
|
+
index: bool = False,
|
|
157
|
+
query_string: Union[str, None] = None,
|
|
158
|
+
create_table_statement: Union[str, None] = None,
|
|
159
|
+
verbose: bool = True,
|
|
160
|
+
**kwargs,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Exports a Pandas data frame to a ClickHouse warehouse based on the table name.
|
|
164
|
+
If table doesn't exist, the table is automatically created.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
df (DataFrame): Data frame to export
|
|
168
|
+
table_name (str): Name of the table to export data to (excluding database).
|
|
169
|
+
If this table exists, the table schema must match the data frame schema.
|
|
170
|
+
If this table doesn't exist, query_string must be specified to create the new table.
|
|
171
|
+
database (str): Name of the database in which the table is located.
|
|
172
|
+
if_exists (str, optional): Specifies export policy if table exists. Either
|
|
173
|
+
- `'fail'`: throw an error.
|
|
174
|
+
- `'replace'`: drops existing table and creates new table of same name.
|
|
175
|
+
- `'append'`: appends data frame to existing table. In this case the schema must
|
|
176
|
+
match the original table.
|
|
177
|
+
Defaults to `'append'`.
|
|
178
|
+
**kwargs: Additional arguments to pass to writer
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
if type(df) is dict:
|
|
182
|
+
df = DataFrame([df])
|
|
183
|
+
elif type(df) is list:
|
|
184
|
+
df = DataFrame(df)
|
|
185
|
+
|
|
186
|
+
if not query_string:
|
|
187
|
+
if index:
|
|
188
|
+
df = df.reset_index()
|
|
189
|
+
|
|
190
|
+
dtypes = infer_dtypes(df)
|
|
191
|
+
df = clean_df_for_export(df, self.clean, dtypes)
|
|
192
|
+
|
|
193
|
+
def __process(database: Union[str, None]):
|
|
194
|
+
|
|
195
|
+
df_existing = self.client.query_df(f"""
|
|
196
|
+
EXISTS TABLE {database}.{table_name}
|
|
197
|
+
""")
|
|
198
|
+
|
|
199
|
+
table_exists = not df_existing.empty and df_existing.iloc[0, 0] == 1
|
|
200
|
+
should_create_table = not table_exists
|
|
201
|
+
|
|
202
|
+
if table_exists:
|
|
203
|
+
if ExportWritePolicy.FAIL == if_exists:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f'Table \'{table_name}\' already'
|
|
206
|
+
' exists in database {database}.',
|
|
207
|
+
)
|
|
208
|
+
elif ExportWritePolicy.REPLACE == if_exists:
|
|
209
|
+
self.client.command(
|
|
210
|
+
f'DROP TABLE IF EXISTS {database}.{table_name}')
|
|
211
|
+
should_create_table = True
|
|
212
|
+
|
|
213
|
+
if query_string:
|
|
214
|
+
self.client.command(f'USE {database}')
|
|
215
|
+
|
|
216
|
+
if should_create_table:
|
|
217
|
+
self.client.command(f"""
|
|
218
|
+
CREATE TABLE IF NOT EXISTS {database}.{table_name} ENGINE = Memory AS
|
|
219
|
+
{query_string}
|
|
220
|
+
""")
|
|
221
|
+
else:
|
|
222
|
+
self.client.command(f"""
|
|
223
|
+
INSERT INTO {database}.{table_name}
|
|
224
|
+
{query_string}
|
|
225
|
+
""")
|
|
226
|
+
else:
|
|
227
|
+
if should_create_table:
|
|
228
|
+
self.client.command(create_table_statement)
|
|
229
|
+
|
|
230
|
+
self.client.insert_df(f'{database}.{table_name}', df)
|
|
231
|
+
|
|
232
|
+
if verbose:
|
|
233
|
+
with self.printer.print_msg(
|
|
234
|
+
f'Exporting data to table \'{database}.{table_name}\''):
|
|
235
|
+
__process(database=database)
|
|
236
|
+
else:
|
|
237
|
+
__process(database=database)
|