mage-ai 0.9.7__py3-none-any.whl → 0.9.9__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.

Files changed (161) hide show
  1. mage_ai/ai/constants.py +1 -0
  2. mage_ai/ai/generator.py +6 -0
  3. mage_ai/ai/{gen_doc_for_pipeline.py → generator_cmds.py} +5 -0
  4. mage_ai/ai/llm_pipeline_wizard.py +108 -3
  5. mage_ai/api/policies/BlockPolicy.py +1 -0
  6. mage_ai/api/policies/BlockTemplatePolicy.py +9 -0
  7. mage_ai/api/policies/LlmPolicy.py +3 -2
  8. mage_ai/api/policies/OauthAccessTokenPolicy.py +31 -0
  9. mage_ai/api/policies/PipelinePolicy.py +1 -0
  10. mage_ai/api/policies/ProjectPolicy.py +9 -0
  11. mage_ai/api/policies/SearchResultPolicy.py +32 -0
  12. mage_ai/api/policies/VariablePolicy.py +8 -0
  13. mage_ai/api/presenters/BlockTemplatePresenter.py +1 -0
  14. mage_ai/api/presenters/OauthAccessTokenPresenter.py +9 -0
  15. mage_ai/api/presenters/ProjectPresenter.py +2 -0
  16. mage_ai/api/presenters/SearchResultPresenter.py +73 -0
  17. mage_ai/api/resources/BlockResource.py +43 -4
  18. mage_ai/api/resources/BlockTemplateResource.py +15 -2
  19. mage_ai/api/resources/CustomTemplateResource.py +15 -3
  20. mage_ai/api/resources/FileContentResource.py +8 -1
  21. mage_ai/api/resources/FileResource.py +22 -2
  22. mage_ai/api/resources/OauthAccessTokenResource.py +24 -0
  23. mage_ai/api/resources/PipelineResource.py +70 -2
  24. mage_ai/api/resources/ProjectResource.py +15 -1
  25. mage_ai/api/resources/SearchResultResource.py +68 -0
  26. mage_ai/api/resources/VariableResource.py +25 -14
  27. mage_ai/cache/base.py +3 -0
  28. mage_ai/cache/block_action_object/__init__.py +282 -0
  29. mage_ai/cache/block_action_object/constants.py +4 -0
  30. mage_ai/cache/constants.py +1 -0
  31. mage_ai/data_integrations/destinations/constants.py +2 -0
  32. mage_ai/data_preparation/executors/k8s_pipeline_executor.py +1 -1
  33. mage_ai/data_preparation/models/block/__init__.py +20 -1
  34. mage_ai/data_preparation/models/block/dbt/utils/__init__.py +1 -0
  35. mage_ai/data_preparation/models/custom_templates/custom_block_template.py +8 -3
  36. mage_ai/data_preparation/models/custom_templates/utils.py +6 -3
  37. mage_ai/data_preparation/models/file.py +3 -0
  38. mage_ai/data_preparation/models/pipeline.py +10 -1
  39. mage_ai/data_preparation/models/{project.py → project/__init__.py} +17 -0
  40. mage_ai/data_preparation/models/project/constants.py +5 -0
  41. mage_ai/data_preparation/repo_manager.py +9 -6
  42. mage_ai/data_preparation/templates/constants.py +478 -1
  43. mage_ai/data_preparation/templates/template.py +9 -1
  44. mage_ai/orchestration/backfills/service.py +20 -1
  45. mage_ai/orchestration/db/__init__.py +25 -0
  46. mage_ai/orchestration/db/models/schedules.py +20 -1
  47. mage_ai/orchestration/pipeline_scheduler.py +23 -0
  48. mage_ai/server/constants.py +1 -1
  49. mage_ai/server/frontend_dist/404.html +2 -2
  50. mage_ai/server/frontend_dist/404.html.html +2 -2
  51. mage_ai/server/frontend_dist/_next/static/OwvTROmputQCA1Z9HEGDv/_buildManifest.js +1 -0
  52. mage_ai/server/frontend_dist/_next/static/chunks/{1005-ce36fe7ed460d006.js → 1005-16c1a6678de5311e.js} +1 -1
  53. mage_ai/server/frontend_dist/_next/static/chunks/1424-e22139f33196c5c1.js +1 -0
  54. mage_ai/server/frontend_dist/_next/static/chunks/1484-a76b2c31e4808379.js +1 -0
  55. mage_ai/server/frontend_dist/_next/static/chunks/2485-b569baad92049d6b.js +1 -0
  56. mage_ai/server/frontend_dist/_next/static/chunks/{2786-d8b593e5d7c42665.js → 2786-acce6ea0d9b4898e.js} +1 -1
  57. mage_ai/server/frontend_dist/_next/static/chunks/3654-573457b62efff6e2.js +1 -0
  58. mage_ai/server/frontend_dist/_next/static/chunks/3881-0eb04f7f7a244347.js +1 -0
  59. mage_ai/server/frontend_dist/_next/static/chunks/{4317-b112fe7bb532b998.js → 4317-3fe63fc9401685d6.js} +1 -1
  60. mage_ai/server/frontend_dist/_next/static/chunks/{4822-52d9d661b6bd1ae9.js → 4822-045cc7cdd7c95163.js} +1 -1
  61. mage_ai/server/frontend_dist/_next/static/chunks/{547-14029bdacc0ed267.js → 547-4ad2cdae967055b6.js} +1 -1
  62. mage_ai/server/frontend_dist/_next/static/chunks/6299-cf188c1b7a1bc33c.js +1 -0
  63. mage_ai/server/frontend_dist/_next/static/chunks/{7815-6dc55b5fd5d1f48a.js → 7815-fbd99c8f259d9e8b.js} +1 -1
  64. mage_ai/server/frontend_dist/_next/static/chunks/{8312-cdc4d6c3b9f53099.js → 8312-c88a3b1012e5448b.js} +1 -1
  65. mage_ai/server/frontend_dist/_next/static/chunks/{8952-492bf0e29d215ddd.js → 8952-e94a7334fcd5c68b.js} +1 -1
  66. mage_ai/server/frontend_dist/_next/static/chunks/9055-d0d25298cb5f4e45.js +1 -0
  67. mage_ai/server/frontend_dist/_next/static/chunks/pages/{_app-c7557dc9e725825e.js → _app-fa38a24707c76411.js} +1 -1
  68. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-b9dac983c75798f2.js +1 -0
  69. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/{settings-17bb362b9683079c.js → settings-453aaa7bf78f8101.js} +1 -1
  70. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/{[user]-bfa53c0ee79920a0.js → [user]-3b502aa6d1a04cad.js} +1 -1
  71. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/{new-2f7e6bf8e2a37380.js → new-dd41d718cbb5990c.js} +1 -1
  72. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-ec748f838b7b8fad.js +1 -0
  73. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-bcc24120f5911f76.js +1 -0
  74. mage_ai/server/frontend_dist/_next/static/chunks/pages/{overview-8ffbc4b0e8a43266.js → overview-6f20b9e9cbec91d6.js} +1 -1
  75. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/{[...slug]-373f6b8c9161e819.js → [...slug]-55212dfc15881ef2.js} +1 -1
  76. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{backfills-226bf0c5c1bfe004.js → backfills-6b4a82cd0516e121.js} +1 -1
  77. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-0705c99df236ec8a.js +1 -0
  78. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{logs-4ade07836fd577c9.js → logs-c294f5d74bd2d50f.js} +1 -1
  79. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{monitors-fd9eb711e89425e9.js → monitors-1b9e4abc54842cdf.js} +1 -1
  80. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{runs-027666ed18d3151e.js → runs-17ebe87e94242ae5.js} +1 -1
  81. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-48b60ca236a26818.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{syncs-de58e6b91e5e4552.js → syncs-5153f4665aaf8b17.js} +1 -1
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/{[...slug]-432843576e1ed09e.js → [...slug]-1306e85191f6b47c.js} +1 -1
  84. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-ae31b1d0d8a4634d.js → triggers-2e731d343a26d46a.js} +1 -1
  85. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-0fab737dbc9074fc.js +1 -0
  86. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-b2354688508b0755.js +1 -0
  87. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-a4f01b5c429757cc.js → users-6522c963087f18e8.js} +1 -1
  88. mage_ai/server/frontend_dist/_next/static/chunks/pages/{sign-in-1140f8ee7e89381a.js → sign-in-7dcd1257ca9660bb.js} +1 -1
  89. mage_ai/server/frontend_dist/_next/static/chunks/pages/templates/{[...slug]-f59b3f04c5a49351.js → [...slug]-78a07e7fe8973811.js} +1 -1
  90. mage_ai/server/frontend_dist/_next/static/chunks/pages/{templates-dbb209ce131390a9.js → templates-19df643f52679d5a.js} +1 -1
  91. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-a1cf445565b68809.js +1 -0
  92. mage_ai/server/frontend_dist/files.html +2 -2
  93. mage_ai/server/frontend_dist/index.html +2 -2
  94. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  95. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  96. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  97. mage_ai/server/frontend_dist/manage/users.html +2 -2
  98. mage_ai/server/frontend_dist/manage.html +2 -2
  99. mage_ai/server/frontend_dist/overview.html +2 -2
  100. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  101. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  102. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  103. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  104. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  105. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  106. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  107. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  108. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  109. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  110. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  111. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  112. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  113. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  114. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  115. mage_ai/server/frontend_dist/pipelines.html +2 -2
  116. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  117. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  118. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  119. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  120. mage_ai/server/frontend_dist/settings.html +2 -2
  121. mage_ai/server/frontend_dist/sign-in.html +16 -16
  122. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  123. mage_ai/server/frontend_dist/templates.html +2 -2
  124. mage_ai/server/frontend_dist/terminal.html +2 -2
  125. mage_ai/server/frontend_dist/test.html +2 -2
  126. mage_ai/server/frontend_dist/triggers.html +2 -2
  127. mage_ai/server/frontend_dist/version-control.html +2 -2
  128. mage_ai/server/server.py +5 -0
  129. mage_ai/server/websocket_server.py +1 -0
  130. mage_ai/services/search/__init__.py +0 -0
  131. mage_ai/services/search/block_action_objects.py +33 -0
  132. mage_ai/services/search/constants.py +1 -0
  133. mage_ai/shared/array.py +7 -0
  134. mage_ai/shared/parsers.py +11 -2
  135. mage_ai/tests/shared/test_parsers.py +73 -0
  136. mage_ai/usage_statistics/constants.py +1 -0
  137. mage_ai/usage_statistics/logger.py +19 -1
  138. {mage_ai-0.9.7.dist-info → mage_ai-0.9.9.dist-info}/METADATA +9 -7
  139. {mage_ai-0.9.7.dist-info → mage_ai-0.9.9.dist-info}/RECORD +145 -132
  140. mage_ai/server/frontend_dist/_next/static/chunks/1424-de97c5aa0477894b.js +0 -1
  141. mage_ai/server/frontend_dist/_next/static/chunks/1484-c9d6bc33227070dd.js +0 -1
  142. mage_ai/server/frontend_dist/_next/static/chunks/2485-86c97f516c1abb9f.js +0 -1
  143. mage_ai/server/frontend_dist/_next/static/chunks/3215-c2f5f19b650f9a50.js +0 -1
  144. mage_ai/server/frontend_dist/_next/static/chunks/3654-bff4740b63cf280c.js +0 -1
  145. mage_ai/server/frontend_dist/_next/static/chunks/3881-7f121dbbe6e273cd.js +0 -1
  146. mage_ai/server/frontend_dist/_next/static/chunks/6299-97f283be01adf6a8.js +0 -1
  147. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-daf49c7c6f7e70f8.js +0 -1
  148. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-1f6cb7656299cbd1.js +0 -1
  149. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-4894eabb442a039c.js +0 -1
  150. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-a9bd86bee73b5762.js +0 -1
  151. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-cf51793c2112915c.js +0 -1
  152. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-f1131ffe0d2a4a06.js +0 -1
  153. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-170943ca00445486.js +0 -1
  154. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-2ecda1f1437013f2.js +0 -1
  155. mage_ai/server/frontend_dist/_next/static/tMZjCwLa1LRYRzhFRdKNf/_buildManifest.js +0 -1
  156. /mage_ai/server/frontend_dist/_next/static/{tMZjCwLa1LRYRzhFRdKNf → OwvTROmputQCA1Z9HEGDv}/_middlewareManifest.js +0 -0
  157. /mage_ai/server/frontend_dist/_next/static/{tMZjCwLa1LRYRzhFRdKNf → OwvTROmputQCA1Z9HEGDv}/_ssgManifest.js +0 -0
  158. {mage_ai-0.9.7.dist-info → mage_ai-0.9.9.dist-info}/LICENSE +0 -0
  159. {mage_ai-0.9.7.dist-info → mage_ai-0.9.9.dist-info}/WHEEL +0 -0
  160. {mage_ai-0.9.7.dist-info → mage_ai-0.9.9.dist-info}/entry_points.txt +0 -0
  161. {mage_ai-0.9.7.dist-info → mage_ai-0.9.9.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,8 @@ from typing import Dict
3
3
 
4
4
  from mage_ai.api.errors import ApiError
5
5
  from mage_ai.api.resources.GenericResource import GenericResource
6
+ from mage_ai.cache.block_action_object import BlockActionObjectCache
7
+ from mage_ai.data_preparation.models.block import Block
6
8
  from mage_ai.data_preparation.models.errors import FileExistsError
7
9
  from mage_ai.data_preparation.models.file import File
8
10
  from mage_ai.orchestration.db import safe_db_query
@@ -21,7 +23,7 @@ class FileResource(GenericResource):
21
23
 
22
24
  @classmethod
23
25
  @safe_db_query
24
- def create(self, payload: Dict, user, **kwargs) -> 'FileResource':
26
+ async def create(self, payload: Dict, user, **kwargs) -> 'FileResource':
25
27
  dir_path = payload['dir_path']
26
28
  repo_path = get_repo_path()
27
29
  content = None
@@ -42,6 +44,11 @@ class FileResource(GenericResource):
42
44
  overwrite=payload.get('overwrite', False),
43
45
  )
44
46
 
47
+ block_type = Block.block_type_from_path(dir_path)
48
+ if block_type:
49
+ cache_block_action_object = await BlockActionObjectCache.initialize_cache()
50
+ cache_block_action_object.update_block(block_file_absolute_path=file.file_path)
51
+
45
52
  return self(file, user, **kwargs)
46
53
  except FileExistsError as err:
47
54
  error = ApiError.RESOURCE_INVALID.copy()
@@ -70,6 +77,19 @@ class FileResource(GenericResource):
70
77
  return self.model.delete()
71
78
 
72
79
  @safe_db_query
73
- def update(self, payload, **kwargs):
80
+ async def update(self, payload, **kwargs):
81
+ block_type = Block.block_type_from_path(self.model.dir_path)
82
+ cache_block_action_object = None
83
+ if block_type:
84
+ cache_block_action_object = await BlockActionObjectCache.initialize_cache()
85
+ cache_block_action_object.update_block(
86
+ block_file_absolute_path=self.model.file_path,
87
+ remove=True,
88
+ )
89
+
74
90
  self.model.rename(payload['dir_path'], payload['name'])
91
+
92
+ if block_type and cache_block_action_object:
93
+ cache_block_action_object.update_block(block_file_absolute_path=self.model.file_path)
94
+
75
95
  return self
@@ -0,0 +1,24 @@
1
+ from datetime import datetime
2
+ from mage_ai.api.resources.DatabaseResource import DatabaseResource
3
+ from mage_ai.orchestration.db import safe_db_query
4
+ from mage_ai.orchestration.db.models.oauth import Oauth2AccessToken
5
+
6
+
7
+ class OauthAccessTokenResource(DatabaseResource):
8
+ model_class = Oauth2AccessToken
9
+
10
+ @classmethod
11
+ @safe_db_query
12
+ def collection(self, query_arg, meta, user, **kwargs):
13
+ results = Oauth2AccessToken.query.filter(Oauth2AccessToken.user_id == user.id)
14
+
15
+ show_all = query_arg.get('show_all', [None])
16
+ if show_all:
17
+ show_all = show_all[0]
18
+
19
+ if not show_all:
20
+ results = results.filter(Oauth2AccessToken.expires > datetime.utcnow())
21
+
22
+ results = results.order_by(Oauth2AccessToken.expires.desc())
23
+
24
+ return results
@@ -3,12 +3,19 @@ import asyncio
3
3
  from sqlalchemy import or_
4
4
  from sqlalchemy.orm import aliased
5
5
 
6
+ from mage_ai.ai.constants import LLMUseCase
6
7
  from mage_ai.api.operations.constants import DELETE
7
8
  from mage_ai.api.resources.BaseResource import BaseResource
9
+ from mage_ai.api.resources.BlockResource import BlockResource
10
+ from mage_ai.api.resources.LlmResource import LlmResource
8
11
  from mage_ai.data_preparation.models.block.dbt.utils import (
9
12
  add_blocks_upstream_from_refs,
10
13
  )
11
- from mage_ai.data_preparation.models.constants import PipelineStatus
14
+ from mage_ai.data_preparation.models.constants import (
15
+ BlockLanguage,
16
+ BlockType,
17
+ PipelineStatus,
18
+ )
12
19
  from mage_ai.data_preparation.models.custom_templates.custom_pipeline_template import (
13
20
  CustomPipelineTemplate,
14
21
  )
@@ -23,7 +30,8 @@ from mage_ai.orchestration.pipeline_scheduler import (
23
30
  from mage_ai.server.active_kernel import switch_active_kernel
24
31
  from mage_ai.server.kernels import PIPELINE_TO_KERNEL_NAME
25
32
  from mage_ai.settings.repo import get_repo_path
26
- from mage_ai.shared.hash import group_by, ignore_keys
33
+ from mage_ai.shared.array import find_index
34
+ from mage_ai.shared.hash import group_by, ignore_keys, merge_dict
27
35
  from mage_ai.usage_statistics.logger import UsageStatisticLogger
28
36
 
29
37
 
@@ -227,6 +235,66 @@ class PipelineResource(BaseResource):
227
235
  if update_content:
228
236
  update_content = update_content[0]
229
237
 
238
+ llm_payload = payload.get('llm')
239
+ if llm_payload:
240
+ llm_use_case = llm_payload.get('use_case')
241
+ llm_request = llm_payload.get('request')
242
+
243
+ if 'pipeline_uuid' not in llm_payload:
244
+ llm_payload['pipeline_uuid'] = self.model.uuid
245
+
246
+ llm_resource = await LlmResource.create(llm_payload, self.current_user, **kwargs)
247
+ llm_response = llm_resource.model.get('response')
248
+
249
+ pipeline_doc = None
250
+ block_docs = []
251
+ blocks = self.model.blocks_by_uuid.values()
252
+
253
+ async def _add_markdown_block(block_doc: str, block_uuid: str, priority: int):
254
+ return await BlockResource.create(dict(
255
+ content=block_doc.strip() if block_doc else block_doc,
256
+ language=BlockLanguage.MARKDOWN,
257
+ name=f'Documentation for {block_uuid}',
258
+ priority=priority,
259
+ type=BlockType.MARKDOWN,
260
+ ), self.current_user, **merge_dict(kwargs, dict(
261
+ parent_model=self.model,
262
+ )))
263
+
264
+ if LLMUseCase.GENERATE_COMMENT_FOR_BLOCK == llm_use_case:
265
+ pass
266
+ elif LLMUseCase.GENERATE_DOC_FOR_BLOCK == llm_use_case:
267
+ block_doc = llm_response.get('block_doc')
268
+ if block_doc:
269
+ block_uuid = llm_request.get('block_uuid')
270
+ priority = find_index(lambda x: x.uuid == block_uuid, blocks)
271
+ await _add_markdown_block(block_doc, block_uuid, priority)
272
+ elif LLMUseCase.GENERATE_DOC_FOR_PIPELINE == llm_use_case:
273
+ block_docs = llm_response.get('block_docs', [])
274
+ pipeline_doc = llm_response.get('pipeline_doc')
275
+
276
+ if pipeline_doc:
277
+ lines = []
278
+ if payload.get('description'):
279
+ lines.append(payload.get('description'))
280
+ lines.append(pipeline_doc)
281
+ payload['description'] = '\n'.join(lines).strip()
282
+
283
+ if block_docs and len(block_docs) >= 1:
284
+ blocks_with_docs = list(zip(block_docs, blocks))
285
+ blocks_with_docs.reverse()
286
+ blocks_with_docs_length = len(blocks_with_docs)
287
+
288
+ for idx, tup in enumerate(blocks_with_docs):
289
+ priority = blocks_with_docs_length - (idx + 1)
290
+ block_doc, block = tup
291
+
292
+ if block_doc:
293
+ await _add_markdown_block(block_doc, block.uuid, priority)
294
+
295
+ if pipeline_doc:
296
+ await _add_markdown_block(pipeline_doc, self.model.uuid, 0)
297
+
230
298
  await self.model.update(
231
299
  ignore_keys(payload, ['add_upstream_for_block_uuid']),
232
300
  update_content=update_content,
@@ -11,6 +11,7 @@ async def build_project(repo_config=None, **kwargs):
11
11
  project = Project(repo_config=repo_config)
12
12
 
13
13
  model = merge_dict(project.repo_config.to_dict(), dict(
14
+ features=project.features,
14
15
  latest_version=await project.latest_version(),
15
16
  name=project.name,
16
17
  project_uuid=project.project_uuid,
@@ -47,7 +48,15 @@ class ProjectResource(GenericResource):
47
48
  repo_config = get_repo_config()
48
49
 
49
50
  data = {}
50
- should_log_project = False
51
+ should_log_project = self.model.get('help_improve_mage') or False
52
+
53
+ if 'features' in payload:
54
+ for k, v in payload.get('features', {}).items():
55
+ feature = (repo_config.features or {}).get(k)
56
+ if (feature and not v) or (not feature and v):
57
+ if 'features' not in data:
58
+ data['features'] = {}
59
+ data['features'][k] = v
51
60
 
52
61
  if 'help_improve_mage' in payload:
53
62
  if payload['help_improve_mage']:
@@ -58,6 +67,11 @@ class ProjectResource(GenericResource):
58
67
 
59
68
  data['help_improve_mage'] = payload['help_improve_mage']
60
69
 
70
+ if 'openai_api_key' in payload:
71
+ openai_api_key = payload.get('openai_api_key')
72
+ if repo_config.openai_api_key != openai_api_key:
73
+ data['openai_api_key'] = payload.get('openai_api_key')
74
+
61
75
  if len(data.keys()) >= 1:
62
76
  repo_config.save(**data)
63
77
 
@@ -0,0 +1,68 @@
1
+ from mage_ai.api.resources.GenericResource import GenericResource
2
+ from mage_ai.cache.block_action_object.constants import (
3
+ OBJECT_TYPE_BLOCK_FILE,
4
+ OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE,
5
+ OBJECT_TYPE_MAGE_TEMPLATE,
6
+ )
7
+ from mage_ai.data_preparation.models.constants import BlockLanguage, BlockType, PipelineType
8
+ from mage_ai.services.search.constants import SEARCH_TYPE_BLOCK_ACTION_OBJECTS
9
+ from typing import Dict
10
+
11
+
12
+ def filter_results(result: Dict) -> bool:
13
+ block_action_object = result.get('block_action_object')
14
+ object_type = result.get('object_type')
15
+
16
+ block_type = None
17
+ language = None
18
+
19
+ if OBJECT_TYPE_BLOCK_FILE == object_type:
20
+ block_type = block_action_object.get('type')
21
+ language = block_action_object.get('language')
22
+ elif OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE == object_type:
23
+ block_type = block_action_object.get('block_type')
24
+ language = block_action_object.get('language')
25
+ elif OBJECT_TYPE_MAGE_TEMPLATE == object_type:
26
+ block_type = block_action_object.get('block_type')
27
+ language = block_action_object.get('language')
28
+
29
+ if BlockLanguage.YAML == language and BlockType.DBT != block_type:
30
+ return False
31
+
32
+ if block_type in [
33
+ BlockType.CALLBACK,
34
+ BlockType.CONDITIONAL,
35
+ BlockType.EXTENSION,
36
+ ]:
37
+ return False
38
+
39
+ return True
40
+
41
+
42
+ class SearchResultResource(GenericResource):
43
+ @classmethod
44
+ async def create(self, payload: Dict, user, **kwargs):
45
+ pipeline_type = payload.get('pipeline_type', None)
46
+ query = payload.get('query', None)
47
+ ratio = payload.get('ratio', None)
48
+ search_type = payload.get('type', None)
49
+
50
+ results = []
51
+
52
+ if query:
53
+ if SEARCH_TYPE_BLOCK_ACTION_OBJECTS == search_type:
54
+ from mage_ai.services.search.block_action_objects import search
55
+
56
+ results = await search(query, ratio=ratio)
57
+
58
+ # TODO (tommy dangerous): remove this when we unify pipeline types
59
+ if PipelineType.PYTHON == pipeline_type:
60
+ results = list(filter(filter_results, results))
61
+
62
+ results = results[:12]
63
+
64
+ return self(dict(
65
+ results=results,
66
+ type=search_type,
67
+ uuid=query,
68
+ ), user, **kwargs)
@@ -42,9 +42,11 @@ class VariableResource(GenericResource):
42
42
  def collection(self, query, meta, user, **kwargs):
43
43
  pipeline_uuid = kwargs['parent_model'].uuid
44
44
 
45
+ global_only = query.get('global_only', [False])
46
+ if global_only:
47
+ global_only = global_only[0]
48
+
45
49
  # Get global variables from project's path
46
- variable_manager = VariableManager(variables_dir=get_variables_dir())
47
- variables_dict = variable_manager.get_variables_by_pipeline(pipeline_uuid)
48
50
  global_variables = [
49
51
  dict(
50
52
  uuid=uuid,
@@ -60,18 +62,27 @@ class VariableResource(GenericResource):
60
62
  variables=global_variables,
61
63
  )
62
64
  ]
63
- variables = [
64
- dict(
65
- block=dict(uuid=uuid),
66
- pipeline=dict(uuid=pipeline_uuid),
67
- variables=[
68
- get_variable_value(variable_manager, pipeline_uuid, uuid, var) for var in arr
69
- # Not return printed outputs
70
- if var == 'df' or var.startswith('output')
71
- ],
72
- )
73
- for uuid, arr in variables_dict.items() if uuid != 'global'
74
- ] + global_variables_arr
65
+ variables = global_variables_arr
66
+ if not global_only:
67
+ variable_manager = VariableManager(variables_dir=get_variables_dir())
68
+ variables_dict = variable_manager.get_variables_by_pipeline(pipeline_uuid)
69
+ variables = [
70
+ dict(
71
+ block=dict(uuid=uuid),
72
+ pipeline=dict(uuid=pipeline_uuid),
73
+ variables=[
74
+ get_variable_value(
75
+ variable_manager,
76
+ pipeline_uuid,
77
+ uuid,
78
+ var,
79
+ ) for var in arr
80
+ # Not return printed outputs
81
+ if var == 'df' or var.startswith('output')
82
+ ],
83
+ )
84
+ for uuid, arr in variables_dict.items() if uuid != 'global'
85
+ ] + global_variables_arr
75
86
 
76
87
  return self.build_result_set(
77
88
  variables,
mage_ai/cache/base.py CHANGED
@@ -38,6 +38,9 @@ class BaseCache():
38
38
  value_fetched = self.storage.read_json_file(self.__build_path(key), None)
39
39
  return value_fetched
40
40
 
41
+ def load_all_data(self):
42
+ return self.get(self.cache_key)
43
+
41
44
  def set(self, key: str, value: Any) -> None:
42
45
  self.storage.write_json_file(self.__build_path(key), value)
43
46
 
@@ -0,0 +1,282 @@
1
+ import os
2
+ from mage_ai.cache.base import BaseCache
3
+ from mage_ai.cache.block_action_object.constants import (
4
+ OBJECT_TYPE_BLOCK_FILE,
5
+ OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE,
6
+ OBJECT_TYPE_MAGE_TEMPLATE,
7
+ )
8
+ from mage_ai.cache.constants import CACHE_KEY_BLOCK_ACTION_OBJECTS_MAPPING
9
+ from mage_ai.data_preparation.models.block import Block
10
+ from mage_ai.data_preparation.models.constants import (
11
+ BlockLanguage,
12
+ BlockType,
13
+ FILE_EXTENSION_TO_BLOCK_LANGUAGE,
14
+ )
15
+ from mage_ai.data_preparation.models.custom_templates.constants import (
16
+ DIRECTORY_FOR_BLOCK_TEMPLATES,
17
+ )
18
+ from mage_ai.data_preparation.models.custom_templates.custom_block_template import (
19
+ CustomBlockTemplate,
20
+ )
21
+ from mage_ai.data_preparation.models.custom_templates.utils import (
22
+ flatten_files,
23
+ get_templates,
24
+ group_and_hydrate_files,
25
+ )
26
+ from mage_ai.data_preparation.templates.constants import (
27
+ TEMPLATES,
28
+ TEMPLATES_ONLY_FOR_V2,
29
+ )
30
+ from mage_ai.settings.repo import get_repo_path
31
+ from mage_ai.shared.strings import remove_extension_from_filename
32
+ from pathlib import Path
33
+ from typing import Dict
34
+
35
+
36
+ def parse_block_file_absolute_path(block_file_absolute_path: str) -> Dict:
37
+ block_type = Block.block_type_from_path(block_file_absolute_path)
38
+ file_directory_name = Block.file_directory_name(block_type)
39
+
40
+ # This is the block_file_absolute_path without the repo_path
41
+ file_path = str(block_file_absolute_path).replace(get_repo_path(), '')
42
+ if file_path.startswith(os.sep):
43
+ file_path = file_path[1:]
44
+
45
+ file_path_parts = file_path.split(os.path.sep)
46
+
47
+ if len(file_path_parts) >= 1 and file_directory_name == file_path_parts[0]:
48
+ file_path_parts = file_path_parts[1:]
49
+
50
+ # This is the file_path without the block type directory
51
+ filename = os.path.join(*file_path_parts)
52
+
53
+ filename_parts = filename.split('.')
54
+ file_extension = None
55
+ if len(filename_parts) >= 2:
56
+ file_extension = filename_parts[-1]
57
+
58
+ block_language = None
59
+ if file_extension:
60
+ block_language = FILE_EXTENSION_TO_BLOCK_LANGUAGE.get(file_extension)
61
+
62
+ return dict(
63
+ block_type=block_type,
64
+ file_path=file_path,
65
+ filename=filename,
66
+ filename_parts=filename_parts,
67
+ language=block_language,
68
+ )
69
+
70
+
71
+ class BlockActionObjectCache(BaseCache):
72
+ cache_key = CACHE_KEY_BLOCK_ACTION_OBJECTS_MAPPING
73
+
74
+ @classmethod
75
+ async def initialize_cache(self, replace: bool = False) -> 'BlockActionObjectCache':
76
+ cache = self()
77
+ if replace or not cache.exists():
78
+ await cache.initialize_cache_for_all_objects()
79
+
80
+ return cache
81
+
82
+ def build_key_for_block_file(
83
+ self,
84
+ file_path: str = None,
85
+ block_type: BlockType = None,
86
+ language: BlockLanguage = None,
87
+ filename: str = None,
88
+ ) -> str:
89
+ return ':'.join(list(filter(lambda x: x, [
90
+ file_path,
91
+ block_type,
92
+ language,
93
+ remove_extension_from_filename(filename).replace('_', ' ') if filename else filename,
94
+ ])))
95
+
96
+ def build_key_for_custom_block_template(
97
+ self,
98
+ custom_block_template: CustomBlockTemplate,
99
+ ) -> str:
100
+ arr = []
101
+
102
+ # This is a specific order
103
+ for key in [
104
+ 'uuid',
105
+ 'block_type',
106
+ 'language',
107
+ 'name',
108
+ 'description',
109
+ 'tags',
110
+ ]:
111
+ val = getattr(custom_block_template, key)
112
+ if val:
113
+ if not isinstance(val, list):
114
+ val = [val]
115
+ arr += val
116
+
117
+ return ':'.join(arr)
118
+
119
+ def build_key_for_mage_template(self, block_action_object: Dict) -> str:
120
+ arr = []
121
+
122
+ # This is a specific order
123
+ for key in [
124
+ 'path',
125
+ 'block_type',
126
+ 'language',
127
+ 'name',
128
+ 'description',
129
+ 'groups',
130
+ ]:
131
+ val = block_action_object.get(key)
132
+ if val:
133
+ if not isinstance(val, list):
134
+ val = [val]
135
+ arr += val
136
+
137
+ return ':'.join(arr)
138
+
139
+ def update_block(
140
+ self,
141
+ block: Block = None,
142
+ block_file_absolute_path: str = None,
143
+ remove: bool = False,
144
+ ) -> None:
145
+ if block:
146
+ block_type = block.type
147
+ file_path = os.path.join(block.file.dir_path, block.file.filename)
148
+ filename = os.path.join(*file_path.split(os.path.sep)[1:])
149
+ language = block.language
150
+ uuid = block.uuid
151
+ else:
152
+ d = parse_block_file_absolute_path(block_file_absolute_path)
153
+ block_type = d.get('block_type')
154
+ file_path = d.get('file_path')
155
+ filename = d.get('filename')
156
+ language = d.get('language')
157
+
158
+ filename_parts = d.get('filename_parts')
159
+ uuid = '.'.join(filename_parts[:-1])
160
+
161
+ key = self.build_key_for_block_file(
162
+ file_path,
163
+ block_type,
164
+ language,
165
+ filename,
166
+ )
167
+
168
+ mapping = self.load_all_data()
169
+ if mapping is None:
170
+ mapping = {}
171
+
172
+ if OBJECT_TYPE_BLOCK_FILE not in mapping:
173
+ mapping[OBJECT_TYPE_BLOCK_FILE] = {}
174
+
175
+ if remove:
176
+ if key in mapping[OBJECT_TYPE_BLOCK_FILE]:
177
+ mapping[OBJECT_TYPE_BLOCK_FILE].pop(key, None)
178
+ else:
179
+ content = None
180
+ if block:
181
+ content = block.content
182
+ else:
183
+ with open(block_file_absolute_path, 'r') as f:
184
+ content = f.read()
185
+
186
+ mapping[OBJECT_TYPE_BLOCK_FILE][key] = dict(
187
+ content=content,
188
+ file_path=file_path,
189
+ language=language,
190
+ type=block_type,
191
+ uuid=uuid,
192
+ )
193
+
194
+ self.set(self.cache_key, mapping)
195
+
196
+ def update_custom_block_template(
197
+ self,
198
+ custom_block_template: CustomBlockTemplate,
199
+ remove: bool = False,
200
+ ) -> None:
201
+ key = self.build_key_for_custom_block_template(custom_block_template)
202
+
203
+ mapping = self.load_all_data()
204
+ if mapping is None:
205
+ mapping = {}
206
+
207
+ if OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE not in mapping:
208
+ mapping[OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE] = {}
209
+
210
+ if remove:
211
+ if key in mapping[OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE]:
212
+ mapping[OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE].pop(key, None)
213
+ else:
214
+ mapping[OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE][key] = custom_block_template.to_dict(
215
+ include_content=True,
216
+ )
217
+
218
+ self.set(self.cache_key, mapping)
219
+
220
+ async def initialize_cache_for_all_objects(self) -> None:
221
+ mapping = {
222
+ OBJECT_TYPE_BLOCK_FILE: {},
223
+ OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE: {},
224
+ OBJECT_TYPE_MAGE_TEMPLATE: {},
225
+ }
226
+
227
+ for block_action_object in (TEMPLATES + TEMPLATES_ONLY_FOR_V2):
228
+ key = self.build_key_for_mage_template(block_action_object)
229
+ mapping[OBJECT_TYPE_MAGE_TEMPLATE][key] = block_action_object
230
+
231
+ file_dicts = get_templates(DIRECTORY_FOR_BLOCK_TEMPLATES)
232
+ file_dicts_flat = flatten_files(file_dicts)
233
+ custom_block_templates = group_and_hydrate_files(file_dicts_flat, CustomBlockTemplate)
234
+
235
+ for custom_block_template in custom_block_templates:
236
+ key = self.build_key_for_custom_block_template(custom_block_template)
237
+ mapping[OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE][key] = custom_block_template.to_dict(
238
+ include_content=True,
239
+ )
240
+
241
+ for block_type in BlockType:
242
+ file_directory_name = Block.file_directory_name(block_type)
243
+ directory_full_path = os.path.join(get_repo_path(), file_directory_name)
244
+
245
+ for path in Path(directory_full_path).rglob('*'):
246
+ if not path.is_file():
247
+ continue
248
+
249
+ block_file_absolute_path = path.absolute()
250
+ d = parse_block_file_absolute_path(block_file_absolute_path)
251
+
252
+ file_path = d.get('file_path')
253
+ filename = d.get('filename')
254
+ filename_parts = d.get('filename_parts')
255
+ language = d.get('language')
256
+
257
+ if '__init__.py' == filename:
258
+ continue
259
+
260
+ if not language:
261
+ continue
262
+
263
+ key = self.build_key_for_block_file(
264
+ file_path,
265
+ block_type,
266
+ language,
267
+ filename,
268
+ )
269
+
270
+ content = None
271
+ with open(block_file_absolute_path, 'r') as f:
272
+ content = f.read()
273
+
274
+ mapping[OBJECT_TYPE_BLOCK_FILE][key] = dict(
275
+ content=content,
276
+ file_path=file_path,
277
+ language=language,
278
+ type=block_type,
279
+ uuid='.'.join(filename_parts[:-1]),
280
+ )
281
+
282
+ self.set(self.cache_key, mapping)
@@ -0,0 +1,4 @@
1
+ OBJECT_TYPE_BLOCK_FILE = 'block_file'
2
+ OBJECT_TYPE_CUSTOM_BLOCK_TEMPLATE = 'custom_block_template'
3
+ OBJECT_TYPE_GENERATE_BLOCK = 'generate_block'
4
+ OBJECT_TYPE_MAGE_TEMPLATE = 'mage_template'
@@ -1,4 +1,5 @@
1
1
  CACHE_KEY_BLOCKS_TO_PIPELINE_MAPPING = 'blocks_to_pipeline_mapping'
2
+ CACHE_KEY_BLOCK_ACTION_OBJECTS_MAPPING = 'block_action_objects_mapping'
2
3
  CACHE_KEY_TAGS_TO_OBJECT_MAPPING = 'tags_to_object_mapping'
3
4
 
4
5
  MAGE_CACHE_DIRECTORY_DEFAULT = '.cache'
@@ -1,8 +1,10 @@
1
1
  DESTINATIONS = [
2
2
  dict(name='Amazon S3'),
3
3
  dict(name='BigQuery'),
4
+ dict(name='Clickhouse'),
4
5
  dict(name='Delta Lake S3'),
5
6
  dict(name='Google Cloud Storage'),
7
+ dict(name='MongoDB'),
6
8
  dict(
7
9
  module_name='MSSQL',
8
10
  name='Microsoft SQL Server',
@@ -27,7 +27,7 @@ class K8sPipelineExecutor(PipelineExecutor):
27
27
  **kwargs,
28
28
  ) -> None:
29
29
  cmd = f'/app/run_app.sh '\
30
- f'python mage_ai/cli/main.py run {self.pipeline.repo_config.repo_path} '\
30
+ f'mage run {self.pipeline.repo_config.repo_path} '\
31
31
  f'{self.pipeline.uuid}'
32
32
  options = [
33
33
  '--executor-type local_python',