mage-ai 0.9.68__py3-none-any.whl → 0.9.70__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 (316) hide show
  1. mage_ai/api/policies/BackfillPolicy.py +1 -0
  2. mage_ai/api/policies/PipelinePolicy.py +1 -0
  3. mage_ai/api/policies/WorkspacePolicy.py +1 -0
  4. mage_ai/api/presenters/BackfillPresenter.py +1 -0
  5. mage_ai/api/resources/GitBranchResource.py +56 -23
  6. mage_ai/api/resources/GitCustomBranchResource.py +29 -1
  7. mage_ai/api/resources/OauthResource.py +1 -1
  8. mage_ai/api/resources/PipelineResource.py +11 -5
  9. mage_ai/api/resources/PipelineRunResource.py +41 -4
  10. mage_ai/api/resources/PipelineScheduleResource.py +4 -0
  11. mage_ai/api/resources/PullRequestResource.py +6 -4
  12. mage_ai/api/resources/SeedResource.py +2 -1
  13. mage_ai/api/resources/SessionResource.py +13 -1
  14. mage_ai/api/resources/WorkspaceResource.py +5 -4
  15. mage_ai/authentication/permissions/constants.py +2 -0
  16. mage_ai/authentication/permissions/seed.py +32 -21
  17. mage_ai/authentication/providers/active_directory.py +4 -3
  18. mage_ai/authentication/providers/okta.py +22 -83
  19. mage_ai/cache/block_action_object/__init__.py +1 -1
  20. mage_ai/cluster_manager/kubernetes/workload_manager.py +52 -1
  21. mage_ai/cluster_manager/workspace/base.py +6 -0
  22. mage_ai/cluster_manager/workspace/kubernetes.py +22 -1
  23. mage_ai/command_center/applications/utils.py +2 -2
  24. mage_ai/command_center/presenters/text.py +1 -1
  25. mage_ai/data_preparation/executors/k8s_block_executor.py +30 -7
  26. mage_ai/data_preparation/executors/k8s_pipeline_executor.py +30 -7
  27. mage_ai/data_preparation/executors/streaming_pipeline_executor.py +78 -8
  28. mage_ai/data_preparation/git/__init__.py +50 -22
  29. mage_ai/data_preparation/git/api.py +62 -7
  30. mage_ai/data_preparation/git/utils.py +45 -21
  31. mage_ai/data_preparation/models/block/__init__.py +31 -8
  32. mage_ai/data_preparation/models/block/data_integration/mixins.py +16 -5
  33. mage_ai/data_preparation/models/block/dynamic/child.py +3 -0
  34. mage_ai/data_preparation/models/block/dynamic/utils.py +9 -4
  35. mage_ai/data_preparation/models/block/dynamic/variables.py +2 -2
  36. mage_ai/data_preparation/models/block/extension/utils.py +1 -0
  37. mage_ai/data_preparation/models/block/global_data_product/__init__.py +25 -2
  38. mage_ai/data_preparation/models/block/integration/__init__.py +1 -1
  39. mage_ai/data_preparation/models/block/remote/__init__.py +0 -0
  40. mage_ai/data_preparation/models/block/remote/models.py +58 -0
  41. mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
  42. mage_ai/data_preparation/models/block/utils.py +38 -0
  43. mage_ai/data_preparation/models/constants.py +2 -0
  44. mage_ai/data_preparation/models/global_data_product/__init__.py +12 -0
  45. mage_ai/data_preparation/models/pipeline.py +31 -11
  46. mage_ai/data_preparation/models/triggers/__init__.py +4 -2
  47. mage_ai/data_preparation/models/utils.py +6 -0
  48. mage_ai/data_preparation/models/variable.py +18 -4
  49. mage_ai/data_preparation/repo_manager.py +3 -2
  50. mage_ai/data_preparation/shared/utils.py +1 -1
  51. mage_ai/data_preparation/storage/local_storage.py +12 -6
  52. mage_ai/data_preparation/templates/data_exporters/mysql.py +2 -2
  53. mage_ai/data_preparation/templates/data_exporters/oracledb.py +27 -0
  54. mage_ai/data_preparation/templates/repo/metadata.yaml +1 -0
  55. mage_ai/io/bigquery.py +131 -58
  56. mage_ai/io/mysql.py +38 -6
  57. mage_ai/io/snowflake.py +152 -29
  58. mage_ai/orchestration/db/migrations/versions/42a14d6143f1_update_token_column_type.py +54 -0
  59. mage_ai/orchestration/db/models/oauth.py +14 -13
  60. mage_ai/orchestration/db/models/schedules.py +30 -2
  61. mage_ai/orchestration/job_manager.py +6 -0
  62. mage_ai/orchestration/notification/sender.py +37 -15
  63. mage_ai/orchestration/pipeline_scheduler_original.py +48 -31
  64. mage_ai/orchestration/queue/celery_queue.py +8 -1
  65. mage_ai/orchestration/queue/process_queue.py +67 -4
  66. mage_ai/orchestration/queue/queue.py +8 -0
  67. mage_ai/orchestration/triggers/api.py +29 -1
  68. mage_ai/orchestration/triggers/global_data_product.py +9 -4
  69. mage_ai/orchestration/triggers/utils.py +10 -1
  70. mage_ai/orchestration/utils/resources.py +3 -0
  71. mage_ai/server/api/downloads.py +4 -1
  72. mage_ai/server/api/runs.py +151 -0
  73. mage_ai/server/constants.py +1 -1
  74. mage_ai/server/frontend_dist/404.html +6 -6
  75. mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → RhDiJSkcjCsh4xxX4BFBk}/_buildManifest.js +1 -1
  76. mage_ai/server/frontend_dist/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
  77. mage_ai/server/frontend_dist/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  78. mage_ai/server/frontend_dist/_next/static/chunks/3548-9d26185b3fb663b1.js +1 -0
  79. mage_ai/server/frontend_dist/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  80. mage_ai/server/frontend_dist/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  81. mage_ai/server/frontend_dist/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
  84. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  85. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
  86. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-a66b4c7641ae03eb.js → frontend_dist/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
  87. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
  88. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
  89. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
  90. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  91. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
  92. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-cb88fd075a357fcf.js → triggers-4612d15a65c35912.js} +1 -1
  93. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  94. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
  95. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  96. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
  97. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
  98. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  99. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
  100. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
  101. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
  102. mage_ai/server/frontend_dist/block-layout.html +2 -2
  103. mage_ai/server/frontend_dist/compute.html +2 -2
  104. mage_ai/server/frontend_dist/files.html +2 -2
  105. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  106. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  107. mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
  108. mage_ai/server/frontend_dist/global-hooks.html +2 -2
  109. mage_ai/server/frontend_dist/index.html +2 -2
  110. mage_ai/server/frontend_dist/manage/files.html +2 -2
  111. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  112. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  113. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  114. mage_ai/server/frontend_dist/manage/users.html +2 -2
  115. mage_ai/server/frontend_dist/manage.html +2 -2
  116. mage_ai/server/frontend_dist/oauth.html +2 -2
  117. mage_ai/server/frontend_dist/overview.html +2 -2
  118. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  119. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  120. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  121. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  122. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  123. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  124. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  125. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  126. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  127. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  128. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  129. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  130. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  131. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  132. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  133. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  134. mage_ai/server/frontend_dist/pipelines.html +2 -2
  135. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
  136. mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
  137. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  138. mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
  139. mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
  140. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
  141. mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
  142. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  143. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
  144. mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
  145. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  146. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
  147. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  148. mage_ai/server/frontend_dist/settings.html +2 -2
  149. mage_ai/server/frontend_dist/sign-in.html +6 -6
  150. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  151. mage_ai/server/frontend_dist/templates.html +2 -2
  152. mage_ai/server/frontend_dist/terminal.html +2 -2
  153. mage_ai/server/frontend_dist/test.html +2 -2
  154. mage_ai/server/frontend_dist/triggers.html +2 -2
  155. mage_ai/server/frontend_dist/version-control.html +2 -2
  156. mage_ai/server/frontend_dist_base_path_template/404.html +6 -6
  157. mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → TdpLLFome13qvM0gXvpHs}/_buildManifest.js +1 -1
  158. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
  159. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  160. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-9d26185b3fb663b1.js +1 -0
  161. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  162. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  163. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
  164. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
  165. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
  166. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  167. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
  168. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipeline-runs-a66b4c7641ae03eb.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
  169. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
  170. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
  171. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
  172. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  173. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
  174. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-cb88fd075a357fcf.js → triggers-4612d15a65c35912.js} +1 -1
  175. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  176. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
  177. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  178. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
  179. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
  180. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  181. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
  182. mage_ai/server/{frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist_base_path_template/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
  183. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
  184. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  185. mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
  186. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  187. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  188. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  189. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
  190. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
  191. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  192. mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
  193. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  194. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  195. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  196. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  197. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  198. mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
  199. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  200. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  201. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  202. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  203. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
  204. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  205. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  206. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  207. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  208. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  209. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  210. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  211. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  212. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  213. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  214. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  215. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  216. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  217. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
  218. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
  219. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  220. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
  221. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
  222. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
  223. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
  224. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  225. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
  226. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
  227. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  228. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
  229. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  230. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  231. mage_ai/server/frontend_dist_base_path_template/sign-in.html +6 -6
  232. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  233. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  234. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  235. mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
  236. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  237. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  238. mage_ai/server/scheduler_manager.py +9 -0
  239. mage_ai/server/server.py +47 -17
  240. mage_ai/server/utils/output_display.py +2 -2
  241. mage_ai/server/websocket_server.py +1 -0
  242. mage_ai/services/aws/ecs/ecs.py +1 -0
  243. mage_ai/services/k8s/config.py +4 -4
  244. mage_ai/services/k8s/utils.py +97 -0
  245. mage_ai/settings/keys/auth.py +2 -0
  246. mage_ai/settings/server.py +1 -1
  247. mage_ai/shared/parsers.py +6 -1
  248. mage_ai/streaming/sources/influxdb.py +2 -0
  249. mage_ai/streaming/sources/kafka.py +1 -1
  250. mage_ai/tests/api/endpoints/mixins.py +10 -9
  251. mage_ai/tests/api/endpoints/test_seeds.py +24 -0
  252. mage_ai/tests/api/operations/base/mixins.py +1 -1
  253. mage_ai/tests/api/operations/test_sessions.py +53 -2
  254. mage_ai/tests/api/resources/test_pipeline_resource.py +2 -2
  255. mage_ai/tests/authentication/oauth/test_utils.py +1 -1
  256. mage_ai/tests/authentication/providers/test_okta.py +43 -0
  257. mage_ai/tests/data_preparation/models/block/test_global_data_product.py +2 -0
  258. mage_ai/tests/orchestration/db/models/test_oauth.py +3 -3
  259. mage_ai/tests/orchestration/queue/test_process_queue.py +1 -0
  260. mage_ai/tests/orchestration/triggers/test_global_data_product.py +138 -136
  261. mage_ai/tests/server/test_server.py +27 -4
  262. mage_ai/tests/services/k8s/test_job_manager.py +9 -6
  263. mage_ai/version_control/branch/utils.py +2 -1
  264. mage_ai/version_control/models.py +3 -2
  265. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/METADATA +5 -5
  266. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/RECORD +272 -264
  267. mage_ai/server/frontend_dist/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  268. mage_ai/server/frontend_dist/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  269. mage_ai/server/frontend_dist/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
  270. mage_ai/server/frontend_dist/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  271. mage_ai/server/frontend_dist/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  272. mage_ai/server/frontend_dist/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
  273. mage_ai/server/frontend_dist/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
  274. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
  275. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
  276. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
  277. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
  278. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
  279. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
  280. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
  281. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
  282. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
  283. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
  284. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  285. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
  286. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js +0 -1
  287. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
  288. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
  289. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  290. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  291. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
  292. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  293. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  294. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
  295. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
  296. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
  297. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
  298. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
  299. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
  300. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
  301. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
  302. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
  303. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
  304. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
  305. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
  306. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  307. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
  308. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js +0 -1
  309. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
  310. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
  311. /mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → RhDiJSkcjCsh4xxX4BFBk}/_ssgManifest.js +0 -0
  312. /mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → TdpLLFome13qvM0gXvpHs}/_ssgManifest.js +0 -0
  313. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/LICENSE +0 -0
  314. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/WHEEL +0 -0
  315. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/entry_points.txt +0 -0
  316. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/top_level.txt +0 -0
mage_ai/server/server.py CHANGED
@@ -13,6 +13,7 @@ from typing import Optional, Union
13
13
  import pytz
14
14
  import tornado.ioloop
15
15
  import tornado.web
16
+ from sqlalchemy import or_
16
17
  from tornado import autoreload
17
18
  from tornado.ioloop import PeriodicCallback
18
19
  from tornado.log import enable_pretty_logging
@@ -42,7 +43,7 @@ from mage_ai.data_preparation.repo_manager import (
42
43
  )
43
44
  from mage_ai.data_preparation.shared.constants import MANAGE_ENV_VAR
44
45
  from mage_ai.orchestration.constants import Entity
45
- from mage_ai.orchestration.db import db_connection, set_db_schema
46
+ from mage_ai.orchestration.db import db_connection, safe_db_query, set_db_schema
46
47
  from mage_ai.orchestration.db.database_manager import database_manager
47
48
  from mage_ai.orchestration.db.models.oauth import Oauth2Application, Role, User
48
49
  from mage_ai.orchestration.utils.distributed_lock import DistributedLock
@@ -55,6 +56,7 @@ from mage_ai.server.api.events import (
55
56
  ApiEventMatcherDetailHandler,
56
57
  ApiEventMatcherListHandler,
57
58
  )
59
+ from mage_ai.server.api.runs import ApiRunHandler
58
60
  from mage_ai.server.api.triggers import ApiTriggerPipelineHandler
59
61
  from mage_ai.server.api.v1 import (
60
62
  ApiChildDetailHandler,
@@ -321,6 +323,16 @@ def make_app(
321
323
  ApiTriggerPipelineHandler,
322
324
  ),
323
325
 
326
+ # Run a single block and get a response immediately
327
+ (
328
+ r'/api/runs',
329
+ ApiRunHandler,
330
+ ),
331
+ (
332
+ r'/api/runs/(?P<token>\w+)',
333
+ ApiRunHandler,
334
+ ),
335
+
324
336
  # Download block output
325
337
  (
326
338
  r'/api/pipelines/(?P<pipeline_uuid>\w+)/block_outputs/'
@@ -444,6 +456,7 @@ def make_app(
444
456
  )
445
457
 
446
458
 
459
+ @safe_db_query
447
460
  def initialize_user_authentication(project_type: ProjectType) -> Oauth2Application:
448
461
  logger.info('User authentication is enabled.')
449
462
  # We need to sleep for a few seconds after creating all the tables or else there
@@ -452,12 +465,14 @@ def initialize_user_authentication(project_type: ProjectType) -> Oauth2Applicati
452
465
 
453
466
  # Create new roles on existing users. This should only need to be run once.
454
467
  if project_type == ProjectType.SUB:
468
+ project_uuid = get_project_uuid()
469
+ project_uuid_truncated = project_uuid[:8]
455
470
  Role.create_default_roles(
456
471
  entity=Entity.PROJECT,
457
- entity_id=get_project_uuid(),
458
- prefix=get_repo_name(),
472
+ entity_id=project_uuid,
473
+ name_func=lambda role: f'{role}_{project_uuid_truncated}',
459
474
  )
460
- default_owner_role = Role.get_role(f'{get_repo_name()}_{Role.DefaultRole.OWNER}')
475
+ default_owner_role = Role.get_role(f'{Role.DefaultRole.OWNER}_{project_uuid_truncated}')
461
476
  else:
462
477
  Role.create_default_roles()
463
478
  default_owner_role = Role.get_role(Role.DefaultRole.OWNER)
@@ -469,19 +484,29 @@ def initialize_user_authentication(project_type: ProjectType) -> Oauth2Applicati
469
484
  if not legacy_owner_user and len(owner_users) == 0:
470
485
  logger.info('User with owner permission doesn’t exist, creating owner user.')
471
486
  if AUTHENTICATION_MODE.lower() == 'ldap':
472
- user = User.create(
473
- roles_new=[default_owner_role],
474
- username=get_settings_value(LDAP_ADMIN_USERNAME, 'admin'),
475
- )
487
+ username = get_settings_value(LDAP_ADMIN_USERNAME, 'admin')
488
+ user = User.query.filter(User.username == username).first()
489
+ if not user:
490
+ user = User.create(
491
+ roles_new=[default_owner_role],
492
+ username=get_settings_value(LDAP_ADMIN_USERNAME, 'admin'),
493
+ )
476
494
  else:
477
495
  password_salt = generate_salt()
478
- user = User.create(
479
- email='admin@admin.com',
480
- password_hash=create_bcrypt_hash('admin', password_salt),
481
- password_salt=password_salt,
482
- roles_new=[default_owner_role],
483
- username='admin',
484
- )
496
+ user = User.query.filter(
497
+ or_(
498
+ User.email == 'admin@admin.com',
499
+ User.username == 'admin',
500
+ ),
501
+ ).first()
502
+ if not user:
503
+ user = User.create(
504
+ email='admin@admin.com',
505
+ password_hash=create_bcrypt_hash('admin', password_salt),
506
+ password_salt=password_salt,
507
+ roles_new=[default_owner_role],
508
+ username='admin',
509
+ )
485
510
  owner_user = user
486
511
  else:
487
512
  if legacy_owner_user and not legacy_owner_user.roles_new:
@@ -573,7 +598,7 @@ async def main(
573
598
  from mage_ai.data_preparation.sync.git_sync import GitSync
574
599
  sync_config = GitConfig.load(config=preferences.sync_config)
575
600
  sync = GitSync(sync_config, setup_repo=True)
576
- if sync_config.sync_on_start:
601
+ if sync_config.remote_repo_link and sync_config.sync_on_start is True:
577
602
  try:
578
603
  sync.sync_data()
579
604
  logger.info(
@@ -671,7 +696,12 @@ async def main(
671
696
  update_settings_on_metadata_change()
672
697
  observer = Observer()
673
698
  event_handler = MetadataEventHandler()
674
- observer.schedule(event_handler, path=get_metadata_path(root_project=True))
699
+ metadata_file = get_metadata_path(root_project=True)
700
+ if not os.path.exists(metadata_file):
701
+ os.makedirs(os.path.dirname(metadata_file), exist_ok=True)
702
+ with open(metadata_file, 'w') as f:
703
+ f.write('')
704
+ observer.schedule(event_handler, path=metadata_file)
675
705
  observer.start()
676
706
 
677
707
  get_messages(
@@ -208,7 +208,7 @@ def __custom_output():
208
208
  ignore_nan=True,
209
209
  )
210
210
  return print(f'[__internal_output__]{{_json_string}}')
211
- elif bool({has_reduce_output}):
211
+ elif bool({has_reduce_output}) and bool({is_dynamic_child}):
212
212
  _json_string = simplejson.dumps(
213
213
  transform_output_for_display_reduce_output(
214
214
  _internal_output_return,
@@ -460,7 +460,7 @@ def execute_custom_code():
460
460
 
461
461
  output = block_output['output'] or []
462
462
 
463
- if {widget} or is_dynamic_block(block) or is_dynamic_child or has_reduce_output:
463
+ if {widget} or is_dynamic_block(block) or is_dynamic_child:
464
464
  return output
465
465
  else:
466
466
  return find(lambda val: val is not None, output)
@@ -116,6 +116,7 @@ def run_pipeline(
116
116
  pipeline.execute_sync(
117
117
  global_vars=global_vars,
118
118
  build_block_output_stdout=build_block_output_stdout,
119
+ retry_config=dict(),
119
120
  run_sensors=False,
120
121
  )
121
122
  add_pipeline_message(
@@ -16,6 +16,7 @@ def run_task(
16
16
  response = client.run_task(**ecs_config.get_task_config(command=command))
17
17
 
18
18
  print(json.dumps(response, indent=4, default=str))
19
+ wait_for_completion = False if ecs_config.wait_timeout == -1 else wait_for_completion
19
20
 
20
21
  if wait_for_completion:
21
22
  arn = response['tasks'][0]['taskArn']
@@ -2,7 +2,6 @@ from dataclasses import dataclass
2
2
  from typing import Dict
3
3
 
4
4
  from kubernetes.client import (
5
- V1Affinity,
6
5
  V1Container,
7
6
  V1EnvVar,
8
7
  V1LocalObjectReference,
@@ -15,8 +14,9 @@ from kubernetes.client import (
15
14
  )
16
15
 
17
16
  from mage_ai.services.k8s.constants import CONFIG_FILE, DEFAULT_NAMESPACE
17
+ from mage_ai.services.k8s.utils import parse_affinity_config
18
18
  from mage_ai.shared.config import BaseConfig
19
- from mage_ai.shared.hash import camel_case_keys_to_snake_case, get_safe_value
19
+ from mage_ai.shared.hash import get_safe_value
20
20
 
21
21
  # import traceback
22
22
 
@@ -61,8 +61,8 @@ class K8sExecutorConfig(BaseConfig):
61
61
  executor_config.pod.get('service_account_name') or DEFAULT_SERVICE_ACCOUNT_NAME
62
62
  )
63
63
  if executor_config.pod.get('affinity'):
64
- affinity = V1Affinity(
65
- **camel_case_keys_to_snake_case(executor_config.pod['affinity']))
64
+ # Convert the affinity to a V1Affinity object
65
+ affinity = parse_affinity_config(executor_config.pod['affinity'])
66
66
 
67
67
  if executor_config.pod.get('tolerations'):
68
68
  tolerations += [V1Toleration(**e) for e in executor_config.pod['tolerations']]
@@ -0,0 +1,97 @@
1
+ from kubernetes import client
2
+
3
+
4
+ def parse_affinity_config(affinity_config):
5
+ if not affinity_config:
6
+ return None
7
+
8
+ affinity = client.V1Affinity(
9
+ node_affinity=parse_node_affinity(affinity_config.get('nodeAffinity')),
10
+ pod_affinity=parse_pod_affinity(affinity_config.get('podAffinity')),
11
+ pod_anti_affinity=parse_pod_anti_affinity(affinity_config.get('podAntiAffinity'))
12
+ )
13
+ return affinity
14
+
15
+
16
+ def parse_node_affinity(node_affinity_config):
17
+ if not node_affinity_config:
18
+ return None
19
+
20
+ required_terms = node_affinity_config.get(
21
+ 'requiredDuringSchedulingIgnoredDuringExecution', {}).get('nodeSelectorTerms', [])
22
+ node_selector_terms = []
23
+ for term in required_terms:
24
+ match_expressions = [client.V1NodeSelectorRequirement(
25
+ key=expr.get('key'),
26
+ operator=expr.get('operator'),
27
+ values=expr.get('values')
28
+ ) for expr in term.get('matchExpressions', [])]
29
+
30
+ node_selector_terms.append(client.V1NodeSelectorTerm(match_expressions=match_expressions))
31
+
32
+ return client.V1NodeAffinity(
33
+ required_during_scheduling_ignored_during_execution=client.V1NodeSelector(
34
+ node_selector_terms=node_selector_terms)
35
+ )
36
+
37
+
38
+ def parse_pod_affinity(pod_affinity_config):
39
+ if not pod_affinity_config:
40
+ return None
41
+
42
+ required_terms = pod_affinity_config.get('requiredDuringSchedulingIgnoredDuringExecution', [])
43
+ preferred_terms = pod_affinity_config.get('preferredDuringSchedulingIgnoredDuringExecution', [])
44
+
45
+ required_affinity = parse_affinity_term_list(required_terms)
46
+ preferred_affinity = parse_affinity_term_list(preferred_terms)
47
+
48
+ return client.V1PodAffinity(
49
+ required_during_scheduling_ignored_during_execution=required_affinity,
50
+ preferred_during_scheduling_ignored_during_execution=preferred_affinity
51
+ )
52
+
53
+
54
+ def parse_pod_anti_affinity(pod_anti_affinity_config):
55
+ if not pod_anti_affinity_config:
56
+ return None
57
+
58
+ required_terms = pod_anti_affinity_config.get(
59
+ 'requiredDuringSchedulingIgnoredDuringExecution', [])
60
+ preferred_terms = pod_anti_affinity_config.get(
61
+ 'preferredDuringSchedulingIgnoredDuringExecution', [])
62
+
63
+ required_affinity = parse_affinity_term_list(required_terms)
64
+ preferred_affinity = parse_affinity_term_list(preferred_terms)
65
+
66
+ return client.V1PodAntiAffinity(
67
+ required_during_scheduling_ignored_during_execution=required_affinity,
68
+ preferred_during_scheduling_ignored_during_execution=preferred_affinity
69
+ )
70
+
71
+
72
+ def parse_affinity_term_list(term_list):
73
+ if not term_list:
74
+ return None
75
+
76
+ affinity_term_list = []
77
+ for term in term_list:
78
+ label_selector = client.V1LabelSelector(
79
+ match_labels=term.get('labelSelector', {}).get('matchLabels', {}),
80
+ match_expressions=[client.V1LabelSelectorRequirement(
81
+ key=expr.get('key'),
82
+ operator=expr.get('operator'),
83
+ values=expr.get('values')
84
+ ) for expr in term.get('labelSelector', {}).get('matchExpressions', [])]
85
+ )
86
+
87
+ topology_key = term.get('topologyKey')
88
+
89
+ affinity_term_list.append(client.V1WeightedPodAffinityTerm(
90
+ weight=term.get('weight'),
91
+ pod_affinity_term=client.V1PodAffinityTerm(
92
+ label_selector=label_selector,
93
+ topology_key=topology_key
94
+ )
95
+ ))
96
+
97
+ return affinity_term_list
@@ -13,6 +13,8 @@ LDAP_DEFAULT_ACCESS = 'LDAP_DEFAULT_ACCESS'
13
13
  LDAP_GROUP_FIELD = 'LDAP_GROUP_FIELD'
14
14
  LDAP_ROLES_MAPPING = 'LDAP_ROLES_MAPPING'
15
15
 
16
+ UPDATE_ROLES_ON_LOGIN = 'UPDATE_ROLES_ON_LOGIN'
17
+
16
18
  # Git settings
17
19
 
18
20
  GIT_REPO_LINK = 'GIT_REPO_LINK'
@@ -79,7 +79,7 @@ SERVER_LOGGING_TEMPLATE = os.getenv(
79
79
  '%(levelname)s:%(name)s:%(message)s',
80
80
  )
81
81
 
82
- INITIAL_METADATA = os.getenv('INITIAL_METADATA', '{{}}')
82
+ INITIAL_METADATA = os.getenv('INITIAL_METADATA')
83
83
 
84
84
  DISABLE_AUTO_BROWSER_OPEN = get_bool_value(os.getenv('DISABLE_AUTO_BROWSER_OPEN', 'False'))
85
85
 
mage_ai/shared/parsers.py CHANGED
@@ -25,11 +25,13 @@ MAX_ITEMS_IN_SAMPLE_OUTPUT = 20
25
25
 
26
26
 
27
27
  def encode_complex(obj):
28
+ from mage_ai.shared.models import BaseDataClass
29
+
28
30
  if isinstance(obj, set):
29
31
  return list(obj)
30
32
  elif isinstance(obj, BaseModel):
31
33
  return obj.__class__.__name__
32
- elif obj.__class__.__name__ == 'BaseDataClass':
34
+ elif obj.__class__.__name__ == 'BaseDataClass' or isinstance(obj, BaseDataClass):
33
35
  return obj.to_dict()
34
36
  elif isinstance(obj, Enum):
35
37
  return obj.value
@@ -59,6 +61,9 @@ def encode_complex(obj):
59
61
  return obj.to_dict(orient='records')
60
62
  elif isinstance(obj, pd.Series):
61
63
  return obj.to_list()
64
+ # Convert pandas._libs.missing.NAType to None
65
+ elif isinstance(obj, pd._libs.missing.NAType):
66
+ return None
62
67
 
63
68
  return obj
64
69
 
@@ -164,6 +164,7 @@ class InfluxDbSource(BaseSource):
164
164
  'data': {record.get_field(): record.get_value()},
165
165
  'metadata': {
166
166
  'time': int(1e3 * record.get_time().timestamp()),
167
+ 'measurement': record['_measurement'],
167
168
  'tags': {
168
169
  k: v
169
170
  for k, v in record.values.items()
@@ -201,6 +202,7 @@ class InfluxDbSource(BaseSource):
201
202
  'data': {record.get_field(): record.get_value()},
202
203
  'metadata': {
203
204
  'time': int(1e3 * record.get_time().timestamp()),
205
+ 'measurement': record['_measurement'],
204
206
  'tags': {
205
207
  k: v
206
208
  for k, v in record.values.items()
@@ -257,7 +257,7 @@ class KafkaSource(BaseSource):
257
257
 
258
258
  def test_connection(self):
259
259
  self.consumer._client.check_version(timeout=5)
260
- self._print('Test connectino successfully.')
260
+ self._print('Test connection successfully.')
261
261
 
262
262
  def __deserialize_message(self, message):
263
263
  if self.config.serde_config is None:
@@ -813,7 +813,7 @@ class BaseAPIEndpointTest(AsyncDBTestCase):
813
813
  validation = assert_after_create_count(self)
814
814
  self.assertTrue(validation)
815
815
  else:
816
- after_count = len(get_resource().model_class.all())
816
+ after_count = len(get_resource(resource).model_class.all())
817
817
  self.assertEqual(
818
818
  after_count,
819
819
  before_count + 1 if after_create_count is None else after_create_count,
@@ -1096,7 +1096,7 @@ class BaseAPIEndpointTest(AsyncDBTestCase):
1096
1096
 
1097
1097
  self.assertTrue(validation)
1098
1098
  else:
1099
- after_count = len(get_resource().model_class.all())
1099
+ after_count = len(get_resource(resource).model_class.all())
1100
1100
  self.assertEqual(
1101
1101
  after_count,
1102
1102
  before_count - 1 if after_delete_count is None else after_delete_count,
@@ -1132,15 +1132,16 @@ class BaseAPIEndpointTest(AsyncDBTestCase):
1132
1132
  )
1133
1133
  RolePermission.create(permission_id=permission.id, role_id=role.id)
1134
1134
  else:
1135
- permission = Permission.create(
1136
- access=access_for_permissions,
1137
- entity_name=EntityName(entity_name(resource)),
1138
- options=permission_options,
1139
- )
1135
+ e_name = entity_name(resource)
1136
+ if getattr(EntityName, e_name, None) is not None:
1137
+ permission = Permission.create(
1138
+ access=access_for_permissions,
1139
+ entity_name=EntityName(e_name),
1140
+ options=permission_options,
1141
+ )
1142
+ RolePermission.create(permission_id=permission.id, role_id=role.id)
1140
1143
 
1141
1144
  if permission_settings:
1142
1145
  for permission_setting in permission_settings:
1143
1146
  permission_more = Permission.create(**permission_setting)
1144
1147
  RolePermission.create(permission_id=permission_more.id, role_id=role.id)
1145
-
1146
- RolePermission.create(permission_id=permission.id, role_id=role.id)
@@ -0,0 +1,24 @@
1
+ from mage_ai.authentication.permissions.constants import PermissionAccess
2
+ from mage_ai.tests.api.endpoints.mixins import (
3
+ BaseAPIEndpointTest,
4
+ build_create_endpoint_tests,
5
+ )
6
+
7
+
8
+ class SeedAPIEndpointTest(BaseAPIEndpointTest):
9
+ pass
10
+
11
+
12
+ build_create_endpoint_tests(
13
+ SeedAPIEndpointTest,
14
+ resource='seed',
15
+ authentication_accesses=[PermissionAccess.OWNER],
16
+ permissions_accesses=[PermissionAccess.OWNER],
17
+ build_payload=lambda self: dict(
18
+ roles=True,
19
+ permissions=True,
20
+ policy_names=['Block', 'Pipeline', 'Download'],
21
+ ),
22
+ assert_before_create_count=lambda self: True,
23
+ assert_after_create_count=lambda self: True,
24
+ )
@@ -34,7 +34,7 @@ class BaseOperationMixin:
34
34
  self.faker.unique.random_int(),
35
35
  ],
36
36
  )
37
- self.user = User(username=self.faker.unique.name())
37
+ self.user = User.create(username=self.faker.unique.name())
38
38
 
39
39
  self.tags = [
40
40
  dict(
@@ -178,7 +178,9 @@ class SessionOperationTests(BaseApiTestCase):
178
178
  @patch('mage_ai.api.resources.SessionResource.AUTHENTICATION_MODE', 'ldap')
179
179
  @patch.object(LDAPConnection, 'authorize')
180
180
  @patch.object(LDAPConnection, 'authenticate')
181
- async def test_ldap_login_with_role_mapping(self, mock_authenticate, mock_authorize):
181
+ async def test_ldap_login_with_role_mapping(
182
+ self, mock_authenticate, mock_authorize
183
+ ):
182
184
  mock_authenticate.return_value = (
183
185
  True,
184
186
  "Yami_Sukehiro",
@@ -188,7 +190,9 @@ class SessionOperationTests(BaseApiTestCase):
188
190
 
189
191
  Role.create_default_roles()
190
192
 
191
- with patch.dict(os.environ, dict(LDAP_ROLES_MAPPING=json.dumps(dict(Admin=['Admin'])))):
193
+ with patch.dict(
194
+ os.environ, dict(LDAP_ROLES_MAPPING=json.dumps(dict(Admin=['Admin'])))
195
+ ):
192
196
  username = self.faker.email()
193
197
  operation = self.build_operation(
194
198
  action=constants.CREATE,
@@ -208,3 +212,50 @@ class SessionOperationTests(BaseApiTestCase):
208
212
  user = User.query.filter(User.username == username).first()
209
213
  self.assertIsNotNone(user)
210
214
  self.assertEqual(user.roles_new[0].name, 'Admin')
215
+
216
+ @patch('mage_ai.api.resources.SessionResource.AUTHENTICATION_MODE', 'ldap')
217
+ @patch.object(LDAPConnection, 'authorize')
218
+ @patch.object(LDAPConnection, 'authenticate')
219
+ async def test_ldap_update_roles_on_login(self, mock_authenticate, mock_authorize):
220
+ mock_authenticate.return_value = (
221
+ True,
222
+ "Yami_Sukehiro",
223
+ dict(memberOf=['Admin']),
224
+ )
225
+ mock_authorize.return_value = True
226
+
227
+ Role.create_default_roles()
228
+
229
+ username = self.faker.email()
230
+
231
+ User.create(
232
+ username=username,
233
+ email=username,
234
+ roles_new=[Role.query.filter(Role.name == 'Admin').first()],
235
+ )
236
+
237
+ with patch.dict(
238
+ os.environ,
239
+ dict(
240
+ LDAP_ROLES_MAPPING=json.dumps(dict(Admin=['Editor'])),
241
+ UPDATE_ROLES_ON_LOGIN='1',
242
+ ),
243
+ ):
244
+ operation = self.build_operation(
245
+ action=constants.CREATE,
246
+ payload=dict(
247
+ session=dict(
248
+ email=username,
249
+ password="black bull",
250
+ )
251
+ ),
252
+ resource='sessions',
253
+ user=None,
254
+ )
255
+ await operation.execute()
256
+
257
+ mock_authenticate.assert_called_once_with(username, "black bull")
258
+ mock_authorize.assert_called_once_with("Yami_Sukehiro")
259
+ user = User.query.filter(User.username == username).first()
260
+ self.assertIsNotNone(user)
261
+ self.assertEqual(user.roles_new[0].name, 'Editor')
@@ -129,7 +129,7 @@ class PipelineResourceTest(BaseApiTestCase):
129
129
  )
130
130
 
131
131
  mock_get_async.assert_has_calls(
132
- [call.get_async(uuid) for uuid in [
132
+ [call.get_async(uuid, repo_path=self.repo_path) for uuid in [
133
133
  self.pipeline1.uuid,
134
134
  self.pipeline2.uuid,
135
135
  self.pipeline3.uuid,
@@ -172,7 +172,7 @@ class PipelineResourceTest(BaseApiTestCase):
172
172
  )
173
173
 
174
174
  mock_get_async.assert_has_calls(
175
- [call.get_async(uuid) for uuid in [
175
+ [call.get_async(uuid, repo_path=self.repo_path) for uuid in [
176
176
  self.pipeline3.uuid,
177
177
  ]],
178
178
  any_order=True,
@@ -10,7 +10,7 @@ from mage_ai.authentication.oauth.utils import (
10
10
  access_tokens_for_client,
11
11
  refresh_token_for_client,
12
12
  )
13
- from mage_ai.data_preparation.git.api import get_oauth_client_id
13
+ from mage_ai.data_preparation.git.utils import get_oauth_client_id
14
14
  from mage_ai.orchestration.db.models.oauth import Oauth2AccessToken, Oauth2Application
15
15
  from mage_ai.tests.base_test import AsyncDBTestCase
16
16
  from mage_ai.tests.factory import create_user
@@ -0,0 +1,43 @@
1
+ import os
2
+ import unittest
3
+ from unittest.mock import patch
4
+
5
+ from mage_ai.authentication.providers.okta import OktaProvider
6
+
7
+ test_parameters = [
8
+ ('https://samples.auth0.com', 'test-client-id', 'test-client-secret'),
9
+ ('samples.auth0.com', 'test-client-id', 'test-client-secret'),
10
+ ('', 'test-client-id', 'test-client-secret'),
11
+ ('samples.auth0.com', '', ''),
12
+ ]
13
+
14
+
15
+ class OktaProviderTest(unittest.TestCase):
16
+ def setUp(self):
17
+ self.authorization_endpoint = 'https://samples.auth0.com/authorize'
18
+ self.token_endpoint = 'https://samples.auth0.com/oauth/token'
19
+ self.userinfo_endpoint = 'https://samples.auth0.com/userinfo'
20
+
21
+ def test_okta_provider_initialization(self):
22
+ for url, id, secret in test_parameters:
23
+ with self.subTest():
24
+ with patch.dict(
25
+ os.environ,
26
+ dict(
27
+ OKTA_DOMAIN_URL=url,
28
+ OKTA_CLIENT_ID=id,
29
+ OKTA_CLIENT_SECRET=secret,
30
+ ),
31
+ ):
32
+ if not all([url, id]):
33
+ with self.assertRaises(ValueError):
34
+ provider = OktaProvider()
35
+ else:
36
+ provider = OktaProvider()
37
+ self.assertEqual(
38
+ provider.authorization_endpoint, self.authorization_endpoint
39
+ )
40
+ self.assertEqual(provider.token_endpoint, self.token_endpoint)
41
+ self.assertEqual(
42
+ provider.userinfo_endpoint, self.userinfo_endpoint
43
+ )
@@ -72,6 +72,8 @@ class GlobalDataProductBlockTest(DBTestCase):
72
72
  from_notebook=False,
73
73
  logger=ANY,
74
74
  logging_tags=ANY,
75
+ poll_interval=30,
76
+ remote_blocks=None,
75
77
  variables=dict(mage=3),
76
78
  ),
77
79
  ])
@@ -17,7 +17,7 @@ class RoleTests(DBTestCase):
17
17
  Role.create_default_roles(
18
18
  entity=Entity.PROJECT,
19
19
  entity_id=test_entity_id,
20
- prefix='test',
20
+ name_func=lambda x: f'test_{x}',
21
21
  )
22
22
  owner = Role.query.filter(Role.name == 'test_Owner').one_or_none()
23
23
 
@@ -29,12 +29,12 @@ class RoleTests(DBTestCase):
29
29
  Role.create_default_roles(
30
30
  entity=Entity.PROJECT,
31
31
  entity_id=test_entity_id,
32
- prefix='test-2',
32
+ name_func=lambda x: f'test-2_{x}',
33
33
  )
34
34
  Role.create_default_roles(
35
35
  entity=Entity.PROJECT,
36
36
  entity_id=test_entity_id2,
37
- prefix='test-2',
37
+ name_func=lambda x: f'test-2_{x}',
38
38
  )
39
39
  owner = Role.query.filter(Role.name == 'test-2_Owner').one_or_none()
40
40
 
@@ -13,6 +13,7 @@ class ProcessQueueTests(TestCase):
13
13
  def setUp(self):
14
14
  queue_config = QueueConfig.load(config=dict(concurrency=100))
15
15
  self.queue = ProcessQueue(queue_config=queue_config)
16
+ self.queue.start()
16
17
 
17
18
  def test_init(self):
18
19
  self.assertEqual(self.queue.size, 100)