mage-ai 0.9.67__py3-none-any.whl → 0.9.69__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 (307) hide show
  1. mage_ai/api/monitors/BaseMonitor.py +1 -2
  2. mage_ai/api/policies/PipelinePolicy.py +2 -0
  3. mage_ai/api/resources/BlockLayoutItemResource.py +2 -2
  4. mage_ai/api/resources/BlockResource.py +4 -3
  5. mage_ai/api/resources/CacheItemResource.py +1 -1
  6. mage_ai/api/resources/GitBranchResource.py +3 -5
  7. mage_ai/api/resources/PageBlockLayoutResource.py +2 -2
  8. mage_ai/api/resources/PipelineResource.py +13 -0
  9. mage_ai/api/resources/PipelineRunResource.py +10 -1
  10. mage_ai/api/resources/SeedResource.py +2 -1
  11. mage_ai/api/resources/SessionResource.py +13 -1
  12. mage_ai/authentication/permissions/constants.py +2 -0
  13. mage_ai/authentication/permissions/seed.py +32 -21
  14. mage_ai/authentication/providers/active_directory.py +4 -3
  15. mage_ai/authentication/providers/okta.py +22 -83
  16. mage_ai/cache/tag.py +3 -0
  17. mage_ai/cluster_manager/manage.py +4 -1
  18. mage_ai/data_preparation/executors/k8s_block_executor.py +8 -1
  19. mage_ai/data_preparation/executors/k8s_pipeline_executor.py +12 -1
  20. mage_ai/data_preparation/executors/streaming_pipeline_executor.py +77 -7
  21. mage_ai/data_preparation/logging/gcs_logger_manager.py +7 -4
  22. mage_ai/data_preparation/models/block/__init__.py +28 -71
  23. mage_ai/data_preparation/models/block/block_factory.py +77 -0
  24. mage_ai/data_preparation/models/block/data_integration/mixins.py +16 -5
  25. mage_ai/data_preparation/models/block/dbt/block.py +5 -7
  26. mage_ai/data_preparation/models/block/dynamic/child.py +3 -0
  27. mage_ai/data_preparation/models/block/dynamic/variables.py +2 -2
  28. mage_ai/data_preparation/models/block/extension/utils.py +1 -0
  29. mage_ai/data_preparation/models/block/global_data_product/__init__.py +9 -3
  30. mage_ai/data_preparation/models/block/integration/__init__.py +13 -9
  31. mage_ai/data_preparation/models/block/platform/mixins.py +1 -1
  32. mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
  33. mage_ai/data_preparation/models/pipeline.py +102 -19
  34. mage_ai/data_preparation/models/utils.py +6 -0
  35. mage_ai/data_preparation/models/variable.py +18 -4
  36. mage_ai/data_preparation/repo_manager.py +3 -2
  37. mage_ai/data_preparation/shared/utils.py +1 -1
  38. mage_ai/data_preparation/storage/gcs_storage.py +1 -1
  39. mage_ai/data_preparation/templates/constants.py +7 -0
  40. mage_ai/data_preparation/templates/data_exporters/mysql.py +2 -2
  41. mage_ai/data_preparation/templates/data_exporters/oracledb.py +27 -0
  42. mage_ai/data_preparation/templates/repo/metadata.yaml +1 -0
  43. mage_ai/io/bigquery.py +131 -58
  44. mage_ai/io/export_utils.py +3 -0
  45. mage_ai/io/mysql.py +38 -6
  46. mage_ai/io/oracledb.py +138 -3
  47. mage_ai/io/snowflake.py +152 -29
  48. mage_ai/io/sql.py +4 -0
  49. mage_ai/orchestration/db/__init__.py +2 -2
  50. mage_ai/orchestration/db/models/oauth.py +4 -4
  51. mage_ai/orchestration/db/models/schedules.py +10 -3
  52. mage_ai/orchestration/job_manager.py +6 -0
  53. mage_ai/orchestration/notification/sender.py +8 -0
  54. mage_ai/orchestration/pipeline_scheduler_original.py +26 -7
  55. mage_ai/orchestration/queue/celery_queue.py +8 -1
  56. mage_ai/orchestration/queue/process_queue.py +67 -4
  57. mage_ai/orchestration/queue/queue.py +8 -0
  58. mage_ai/server/constants.py +1 -1
  59. mage_ai/server/frontend_dist/404.html +2 -2
  60. mage_ai/server/{frontend_dist_base_path_template/_next/static/khKiaJtwrslgMmp4YSa1f → frontend_dist/_next/static/_krrrgup_C-dPOpX36S8I}/_buildManifest.js +1 -1
  61. mage_ai/server/frontend_dist/_next/static/chunks/1557-df144fbd8b2208c3.js +1 -0
  62. mage_ai/server/frontend_dist/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  63. mage_ai/server/frontend_dist/_next/static/chunks/3548-fa0792ddb88f4646.js +1 -0
  64. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/4241-ccd0126f5750cc35.js → frontend_dist/_next/static/chunks/4241-4499461184ba0d23.js} +1 -1
  65. mage_ai/server/frontend_dist/_next/static/chunks/5627-237a3de578538022.js +1 -0
  66. mage_ai/server/frontend_dist/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  67. mage_ai/server/frontend_dist/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  68. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/7966-5c1786fb7e7a48f5.js → frontend_dist/_next/static/chunks/7966-f07b2913f7326b50.js} +1 -1
  69. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-d9c89527266296f7.js +1 -0
  70. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  71. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
  72. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
  73. mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
  74. mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
  75. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
  76. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +1 -0
  77. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +1 -0
  78. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  79. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-90abafc7ed61c582.js +1 -0
  80. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.js +1 -0
  81. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-503049734a8b082f.js +1 -0
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  84. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-5b26eeda8aed8a7b.js +1 -0
  85. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js → frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-8b793b3b696a2cd3.js} +1 -1
  86. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  87. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js → frontend_dist/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js} +1 -1
  88. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js +1 -0
  89. mage_ai/server/frontend_dist/block-layout.html +2 -2
  90. mage_ai/server/frontend_dist/compute.html +2 -2
  91. mage_ai/server/frontend_dist/files.html +2 -2
  92. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  93. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  94. mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
  95. mage_ai/server/frontend_dist/global-hooks.html +2 -2
  96. mage_ai/server/frontend_dist/index.html +2 -2
  97. mage_ai/server/frontend_dist/manage/files.html +2 -2
  98. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  99. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  100. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  101. mage_ai/server/frontend_dist/manage/users.html +2 -2
  102. mage_ai/server/frontend_dist/manage.html +2 -2
  103. mage_ai/server/frontend_dist/oauth.html +3 -3
  104. mage_ai/server/frontend_dist/overview.html +2 -2
  105. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  106. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  107. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  108. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  109. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  110. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  111. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  112. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  113. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  114. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  115. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  116. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  117. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  118. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  119. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  120. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  121. mage_ai/server/frontend_dist/pipelines.html +2 -2
  122. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
  123. mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
  124. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  125. mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
  126. mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
  127. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
  128. mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
  129. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  130. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
  131. mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
  132. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  133. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
  134. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  135. mage_ai/server/frontend_dist/settings.html +2 -2
  136. mage_ai/server/frontend_dist/sign-in.html +5 -5
  137. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  138. mage_ai/server/frontend_dist/templates.html +2 -2
  139. mage_ai/server/frontend_dist/terminal.html +2 -2
  140. mage_ai/server/frontend_dist/test.html +2 -2
  141. mage_ai/server/frontend_dist/triggers.html +2 -2
  142. mage_ai/server/frontend_dist/version-control.html +2 -2
  143. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  144. mage_ai/server/{frontend_dist/_next/static/vPsMu6Fi2zrHaf2fRXKRO → frontend_dist_base_path_template/_next/static/KLL5mirre9d7_ZeEpaw3s}/_buildManifest.js +1 -1
  145. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-df144fbd8b2208c3.js +1 -0
  146. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  147. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-fa0792ddb88f4646.js +1 -0
  148. mage_ai/server/{frontend_dist/_next/static/chunks/4241-ccd0126f5750cc35.js → frontend_dist_base_path_template/_next/static/chunks/4241-4499461184ba0d23.js} +1 -1
  149. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5627-237a3de578538022.js +1 -0
  150. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  151. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  152. mage_ai/server/{frontend_dist/_next/static/chunks/7966-5c1786fb7e7a48f5.js → frontend_dist_base_path_template/_next/static/chunks/7966-f07b2913f7326b50.js} +1 -1
  153. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-d9c89527266296f7.js +1 -0
  154. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  155. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
  156. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
  157. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
  158. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
  159. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
  160. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +1 -0
  161. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +1 -0
  162. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  163. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-90abafc7ed61c582.js +1 -0
  164. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.js +1 -0
  165. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  166. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-503049734a8b082f.js +1 -0
  167. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  168. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-5b26eeda8aed8a7b.js +1 -0
  169. mage_ai/server/{frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js → frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-8b793b3b696a2cd3.js} +1 -1
  170. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  171. mage_ai/server/{frontend_dist/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js → frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js} +1 -1
  172. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.js +1 -0
  173. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  174. mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
  175. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  176. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  177. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  178. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
  179. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
  180. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  181. mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
  182. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  183. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  184. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  185. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  186. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  187. mage_ai/server/frontend_dist_base_path_template/oauth.html +3 -3
  188. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  189. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  190. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  191. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  192. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
  193. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  194. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  195. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  196. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  197. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  198. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  199. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  200. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  201. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  202. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  203. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  204. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  205. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  206. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
  207. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
  208. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  209. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
  210. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
  211. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
  212. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
  213. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  214. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
  215. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
  216. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  217. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
  218. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  219. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  220. mage_ai/server/frontend_dist_base_path_template/sign-in.html +5 -5
  221. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  222. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  223. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  224. mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
  225. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  226. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  227. mage_ai/server/scheduler_manager.py +7 -0
  228. mage_ai/server/server.py +12 -5
  229. mage_ai/server/websocket_server.py +1 -0
  230. mage_ai/services/k8s/job_manager.py +8 -0
  231. mage_ai/services/slack/slack.py +10 -1
  232. mage_ai/services/spark/spark.py +9 -2
  233. mage_ai/settings/backends.py +8 -8
  234. mage_ai/settings/keys/auth.py +2 -0
  235. mage_ai/settings/models/configuration_option.py +10 -9
  236. mage_ai/settings/server.py +1 -1
  237. mage_ai/shared/files.py +19 -1
  238. mage_ai/shared/path_fixer.py +3 -0
  239. mage_ai/streaming/sources/base.py +5 -0
  240. mage_ai/streaming/sources/influxdb.py +2 -0
  241. mage_ai/streaming/sources/kafka.py +2 -1
  242. mage_ai/streaming/sources/mongodb.py +4 -0
  243. mage_ai/tests/api/endpoints/mixins.py +10 -9
  244. mage_ai/tests/api/endpoints/test_seeds.py +24 -0
  245. mage_ai/tests/api/operations/test_sessions.py +53 -2
  246. mage_ai/tests/authentication/providers/test_okta.py +43 -0
  247. mage_ai/tests/data_preparation/models/block/dbt/test_block.py +2 -2
  248. mage_ai/tests/data_preparation/models/block/dbt/test_block_sql.py +1 -1
  249. mage_ai/tests/data_preparation/models/block/dbt/test_block_yaml.py +1 -1
  250. mage_ai/tests/data_preparation/models/test_block.py +2 -1
  251. mage_ai/tests/orchestration/db/models/test_oauth.py +3 -3
  252. mage_ai/tests/orchestration/queue/test_process_queue.py +1 -0
  253. mage_ai/tests/orchestration/test_pipeline_scheduler.py +2 -0
  254. mage_ai/tests/server/test_server.py +8 -4
  255. mage_ai/tests/settings/models/test_configuration_option.py +2 -2
  256. {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/METADATA +6 -6
  257. {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/RECORD +263 -259
  258. mage_ai/server/frontend_dist/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  259. mage_ai/server/frontend_dist/_next/static/chunks/181-e61915415a976861.js +0 -1
  260. mage_ai/server/frontend_dist/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  261. mage_ai/server/frontend_dist/_next/static/chunks/3548-13563a1ff815f922.js +0 -1
  262. mage_ai/server/frontend_dist/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  263. mage_ai/server/frontend_dist/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  264. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +0 -1
  265. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
  266. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
  267. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
  268. mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
  269. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
  270. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
  271. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
  272. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
  273. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
  274. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
  275. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
  276. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.js +0 -1
  277. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  278. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-dde29a463495cebb.js +0 -1
  279. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
  280. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  281. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/181-e61915415a976861.js +0 -1
  282. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  283. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-13563a1ff815f922.js +0 -1
  284. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  285. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  286. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +0 -1
  287. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
  288. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
  289. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
  290. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
  291. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
  292. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
  293. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
  294. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
  295. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
  296. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
  297. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
  298. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.js +0 -1
  299. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  300. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-dde29a463495cebb.js +0 -1
  301. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
  302. /mage_ai/server/frontend_dist/_next/static/{vPsMu6Fi2zrHaf2fRXKRO → _krrrgup_C-dPOpX36S8I}/_ssgManifest.js +0 -0
  303. /mage_ai/server/frontend_dist_base_path_template/_next/static/{khKiaJtwrslgMmp4YSa1f → KLL5mirre9d7_ZeEpaw3s}/_ssgManifest.js +0 -0
  304. {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/LICENSE +0 -0
  305. {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/WHEEL +0 -0
  306. {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/entry_points.txt +0 -0
  307. {mage_ai-0.9.67.dist-info → mage_ai-0.9.69.dist-info}/top_level.txt +0 -0
mage_ai/io/bigquery.py CHANGED
@@ -1,3 +1,4 @@
1
+ import uuid
1
2
  from typing import Dict, List, Mapping, Union
2
3
 
3
4
  import numpy as np
@@ -14,6 +15,7 @@ from sqlglot import exp, parse_one
14
15
 
15
16
  from mage_ai.io.base import QUERY_ROW_LIMIT, BaseSQLDatabase, ExportWritePolicy
16
17
  from mage_ai.io.config import BaseConfigLoader, ConfigKey
18
+ from mage_ai.io.constants import UNIQUE_CONFLICT_METHOD_UPDATE
17
19
  from mage_ai.io.export_utils import infer_dtypes
18
20
  from mage_ai.shared.custom_logger import DX_PRINTER
19
21
  from mage_ai.shared.environments import is_debug
@@ -200,6 +202,9 @@ WHERE TABLE_NAME = '{table_name}'
200
202
  overwrite_types: Dict = None,
201
203
  query_string: Union[str, None] = None,
202
204
  verbose: bool = True,
205
+ unique_conflict_method: str = None,
206
+ unique_constraints: List[str] = None,
207
+ write_disposition: str = None,
203
208
  **configuration_params,
204
209
  ) -> None:
205
210
  """
@@ -230,24 +235,23 @@ WHERE TABLE_NAME = '{table_name}'
230
235
  elif type(df) is list:
231
236
  df = DataFrame(df)
232
237
 
233
- def __process(database: Union[str, None]):
234
- if query_string:
235
- parts = table_id.split('.')
236
- if len(parts) == 2:
237
- schema, table_name = parts
238
- elif len(parts) == 3:
239
- database, schema, table_name = parts
238
+ def __process(database: Union[str, None], write_disposition: str = None):
239
+ parts = table_id.split('.')
240
+ if len(parts) == 2:
241
+ schema, table_name = parts
242
+ elif len(parts) == 3:
243
+ database, schema, table_name = parts
240
244
 
241
- df_existing = self.client.query(f"""
245
+ df_existing = self.client.query(f"""
242
246
  SELECT 1
243
247
  FROM `{database}.{schema}.__TABLES_SUMMARY__`
244
248
  WHERE table_id = '{table_name}'
245
249
  """).to_dataframe()
246
250
 
247
- full_table_name = f'`{database}.{schema}.{table_name}`'
248
-
249
- table_doesnt_exist = df_existing.empty
251
+ full_table_name = f'`{database}.{schema}.{table_name}`'
250
252
 
253
+ table_doesnt_exist = df_existing.empty
254
+ if query_string:
251
255
  if ExportWritePolicy.FAIL == if_exists and not table_doesnt_exist:
252
256
  raise ValueError(
253
257
  f'Table \'{full_table_name}\' already exists in database.',
@@ -268,60 +272,129 @@ WHERE table_id = '{table_name}'
268
272
  self.client.query(sql)
269
273
 
270
274
  else:
271
- config = LoadJobConfig(**configuration_params)
272
- if overwrite_types is not None:
273
- config.schema = [SchemaField(k, v) for k, v in overwrite_types.items()]
274
- if 'write_disposition' not in configuration_params:
275
- if if_exists == 'replace':
276
- config.write_disposition = WriteDisposition.WRITE_TRUNCATE
277
- elif if_exists == 'append':
278
- config.write_disposition = WriteDisposition.WRITE_APPEND
279
- elif if_exists == 'fail':
280
- config.write_disposition = WriteDisposition.WRITE_EMPTY
281
- else:
282
- raise ValueError(
283
- f'Invalid policy specified for handling existence of '
284
- f'table: \'{if_exists}\''
275
+ if (
276
+ if_exists == ExportWritePolicy.APPEND
277
+ and not table_doesnt_exist
278
+ and unique_constraints
279
+ and unique_conflict_method
280
+ ):
281
+ temp_table_id = f'{table_id}_{uuid.uuid4().hex}'
282
+
283
+ try:
284
+ self.__write_table(
285
+ df,
286
+ temp_table_id,
287
+ overwrite_types=overwrite_types,
288
+ **configuration_params,
289
+ )
290
+
291
+ parts = temp_table_id.split('.')
292
+ if len(parts) == 2:
293
+ temp_table_name = parts[1]
294
+ elif len(parts) == 3:
295
+ temp_table_name = parts[2]
296
+ column_types = self.get_column_types(schema, temp_table_name)
297
+ columns = list(column_types.keys())
298
+ if not columns:
299
+ columns = df.columns.str.replace(' ', '_')
300
+
301
+ on_conditions = []
302
+ for col in unique_constraints:
303
+ on_conditions.append(
304
+ f'((a.{col} IS NULL AND b.{col} IS NULL) OR a.{col} = b.{col})',
305
+ )
306
+
307
+ insert_columns = ', '.join([f'`{col}`' for col in columns])
308
+
309
+ merge_commands = [
310
+ f'MERGE INTO `{table_id}` AS a',
311
+ f'USING (SELECT * FROM `{temp_table_id}`) AS b',
312
+ f"ON {' AND '.join(on_conditions)}",
313
+ ]
314
+
315
+ if UNIQUE_CONFLICT_METHOD_UPDATE == unique_conflict_method:
316
+ set_command = ', '.join(
317
+ [f'a.`{col}` = b.`{col}`' for col in columns],
318
+ )
319
+ merge_commands.append(f'WHEN MATCHED THEN UPDATE SET {set_command}')
320
+
321
+ merge_values = f"({', '.join([f'b.`{col}`' for col in columns])})"
322
+ merge_commands.append(
323
+ f'WHEN NOT MATCHED THEN INSERT ({insert_columns}) VALUES {merge_values}', # noqa: E501
285
324
  )
286
- parts = table_id.split('.')
287
- if len(parts) == 2:
288
- schema = parts[0]
289
- table_name = parts[1]
290
- elif len(parts) == 3:
291
- schema = parts[1]
292
- table_name = parts[2]
293
-
294
- self.client.create_dataset(dataset=schema, exists_ok=True)
295
-
296
- column_types = self.get_column_types(schema, table_name)
297
-
298
- if df is not None:
299
- df.fillna(value=np.NaN, inplace=True)
300
- for col in df.columns:
301
- col_type = column_types.get(col)
302
- if not col_type:
303
- continue
304
-
305
- null_rows = df[col].isnull()
306
- if col_type.startswith('ARRAY<STRUCT'):
307
- df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [{}])
308
- elif col_type.startswith('ARRAY'):
309
- df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [])
310
- elif col_type.startswith('STRUCT'):
311
- df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: {})
312
-
313
- # Clean column names
314
- if type(df) is DataFrame:
315
- df.columns = df.columns.str.replace(' ', '_')
316
-
317
- self.client.load_table_from_dataframe(df, table_id, job_config=config).result()
325
+
326
+ merge_command = '\n'.join(merge_commands)
327
+
328
+ self.client.query(merge_command).result()
329
+ finally:
330
+ self.client.query(f'DROP TABLE IF EXISTS {temp_table_id}').result()
331
+ else:
332
+ if not write_disposition:
333
+ if if_exists == ExportWritePolicy.APPEND:
334
+ write_disposition = WriteDisposition.WRITE_APPEND
335
+ elif if_exists == ExportWritePolicy.REPLACE:
336
+ write_disposition = WriteDisposition.WRITE_TRUNCATE
337
+ elif if_exists == ExportWritePolicy.FAIL:
338
+ write_disposition = WriteDisposition.WRITE_EMPTY
339
+ self.__write_table(
340
+ df,
341
+ table_id,
342
+ overwrite_types=overwrite_types,
343
+ write_disposition=write_disposition,
344
+ **configuration_params,
345
+ )
318
346
 
319
347
  if verbose:
320
348
  with self.printer.print_msg(f'Exporting data to table \'{table_id}\''):
321
- __process(database=database)
349
+ __process(database=database, write_disposition=write_disposition)
322
350
  else:
323
351
  __process(database=database)
324
352
 
353
+ def __write_table(
354
+ self,
355
+ df: DataFrame,
356
+ table_id: str,
357
+ overwrite_types: Dict = None,
358
+ **configuration_params,
359
+ ):
360
+ config = LoadJobConfig(**configuration_params)
361
+ if overwrite_types is not None:
362
+ config.schema = [SchemaField(k, v) for k, v in overwrite_types.items()]
363
+ if not config.write_disposition:
364
+ config.write_disposition = WriteDisposition.WRITE_APPEND
365
+ parts = table_id.split('.')
366
+ if len(parts) == 2:
367
+ schema = parts[0]
368
+ table_name = parts[1]
369
+ elif len(parts) == 3:
370
+ schema = parts[1]
371
+ table_name = parts[2]
372
+
373
+ self.client.create_dataset(dataset=schema, exists_ok=True)
374
+
375
+ column_types = self.get_column_types(schema, table_name)
376
+
377
+ if df is not None:
378
+ df.fillna(value=np.NaN, inplace=True)
379
+ for col in df.columns:
380
+ col_type = column_types.get(col)
381
+ if not col_type:
382
+ continue
383
+
384
+ null_rows = df[col].isnull()
385
+ if col_type.startswith('ARRAY<STRUCT'):
386
+ df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [{}])
387
+ elif col_type.startswith('ARRAY'):
388
+ df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: [])
389
+ elif col_type.startswith('STRUCT'):
390
+ df.loc[null_rows, col] = df.loc[null_rows, col].apply(lambda x: {})
391
+
392
+ # Clean column names
393
+ if type(df) is DataFrame:
394
+ df.columns = df.columns.str.replace(' ', '_')
395
+
396
+ return self.client.load_table_from_dataframe(df, table_id, job_config=config).result()
397
+
325
398
  def execute(self, query_string: str, **kwargs) -> None:
326
399
  """
327
400
  Sends query to the connected BigQuery warehouse.
@@ -108,6 +108,7 @@ def gen_table_creation_query(
108
108
  case_sensitive: bool = False,
109
109
  unique_constraints: List[str] = None,
110
110
  overwrite_types: Dict = None,
111
+ skip_semicolon_at_end: bool = False,
111
112
  ) -> str:
112
113
  """
113
114
  Generates a database table creation query from a data frame.
@@ -157,4 +158,6 @@ def gen_table_creation_query(
157
158
  query.append(
158
159
  f"CONSTRAINT {index_name} UNIQUE ({', '.join(unique_constraints_escaped)})",
159
160
  )
161
+ if skip_semicolon_at_end:
162
+ return f'CREATE TABLE {full_table_name} (' + ','.join(query) + ')'
160
163
  return f'CREATE TABLE {full_table_name} (' + ','.join(query) + ');'
mage_ai/io/mysql.py CHANGED
@@ -8,6 +8,7 @@ from mysql.connector.cursor import MySQLCursor
8
8
  from pandas import DataFrame, Series
9
9
 
10
10
  from mage_ai.io.config import BaseConfigLoader, ConfigKey
11
+ from mage_ai.io.constants import UNIQUE_CONFLICT_METHOD_UPDATE
11
12
  from mage_ai.io.export_utils import PandasTypes
12
13
  from mage_ai.io.sql import BaseSQL
13
14
  from mage_ai.shared.parsers import encode_complex
@@ -60,7 +61,7 @@ class MySQL(BaseSQL):
60
61
  ) -> str:
61
62
  if unique_constraints is None:
62
63
  unique_constraints = []
63
- query = []
64
+ columns_and_types = []
64
65
  for cname in dtypes:
65
66
  if overwrite_types is not None and cname in overwrite_types.keys():
66
67
  dtypes[cname] = overwrite_types[cname]
@@ -68,9 +69,22 @@ class MySQL(BaseSQL):
68
69
  cleaned_col_name = clean_name(cname, case_sensitive=case_sensitive)
69
70
  else:
70
71
  cleaned_col_name = cname
71
- query.append(f'`{cleaned_col_name}` {dtypes[cname]} NULL')
72
-
73
- return f'CREATE TABLE {table_name} (' + ','.join(query) + ');'
72
+ columns_and_types.append(f'`{cleaned_col_name}` {dtypes[cname]} NULL')
73
+
74
+ if unique_constraints:
75
+ unique_constraints = [
76
+ clean_name(col, case_sensitive=case_sensitive)
77
+ for col in unique_constraints
78
+ ]
79
+ index_name = '_'.join([
80
+ clean_name(table_name, case_sensitive=case_sensitive),
81
+ ] + unique_constraints)
82
+ index_name = f'unique{index_name}'[:64]
83
+ columns_and_types.append(
84
+ f"CONSTRAINT {index_name} Unique({', '.join(unique_constraints)})"
85
+ )
86
+
87
+ return f'CREATE TABLE {table_name} (' + ','.join(columns_and_types) + ');'
74
88
 
75
89
  def open(self) -> None:
76
90
  with self.printer.print_msg('Opening connection to MySQL database'):
@@ -94,6 +108,9 @@ class MySQL(BaseSQL):
94
108
  dtypes: List[str],
95
109
  full_table_name: str,
96
110
  buffer: Union[IO, None] = None,
111
+ case_sensitive: bool = False,
112
+ unique_constraints: List[str] = None,
113
+ unique_conflict_method: str = None,
97
114
  **kwargs,
98
115
  ) -> None:
99
116
  def serialize_obj(val):
@@ -133,9 +150,24 @@ class MySQL(BaseSQL):
133
150
  for _, row in df_.iterrows():
134
151
  values.append(tuple([str(val) if type(val) is pd.Timestamp else val for val in row]))
135
152
 
136
- insert_columns = ', '.join([f'`{col}`'for col in columns])
153
+ cleaned_columns = [clean_name(col, case_sensitive=case_sensitive) for col in columns]
154
+ insert_columns = ', '.join([f'`{col}`'for col in cleaned_columns])
155
+
156
+ query = [
157
+ f'INSERT INTO {full_table_name} ({insert_columns})',
158
+ f'VALUES ({values_placeholder})',
159
+ ]
160
+
161
+ if unique_constraints and unique_conflict_method:
162
+ if UNIQUE_CONFLICT_METHOD_UPDATE == unique_conflict_method:
163
+ update_command = [f'{col} = new.{col}' for col in cleaned_columns]
164
+ query += [
165
+ 'AS new',
166
+ f"ON DUPLICATE KEY UPDATE {', '.join(update_command)}",
167
+ ]
168
+
169
+ sql = '\n'.join(query)
137
170
 
138
- sql = f'INSERT INTO {full_table_name} ({insert_columns}) VALUES ({values_placeholder})'
139
171
  cursor.executemany(sql, values)
140
172
 
141
173
  def get_type(self, column: Series, dtype: str) -> str:
mage_ai/io/oracledb.py CHANGED
@@ -1,13 +1,17 @@
1
1
  import warnings
2
- from typing import Union
2
+ from typing import IO, Any, List, Union
3
3
 
4
+ import numpy as np
4
5
  import oracledb
5
- from pandas import DataFrame, read_sql
6
+ import simplejson
7
+ from pandas import DataFrame, Series, read_sql
6
8
 
7
- from mage_ai.io.base import QUERY_ROW_LIMIT
9
+ from mage_ai.io.base import QUERY_ROW_LIMIT, ExportWritePolicy
8
10
  from mage_ai.io.config import BaseConfigLoader, ConfigKey
11
+ from mage_ai.io.export_utils import PandasTypes
9
12
  from mage_ai.io.sql import BaseSQL
10
13
  from mage_ai.server.logger import Logger
14
+ from mage_ai.shared.parsers import encode_complex
11
15
 
12
16
  logger = Logger().new_server_logger(__name__)
13
17
 
@@ -122,3 +126,134 @@ SELECT *
122
126
  FROM subquery
123
127
  FETCH FIRST {limit} ROWS ONLY
124
128
  """
129
+
130
+ def table_exists(self, schema_name: str, table_name: str) -> bool:
131
+ with self.conn.cursor() as cur:
132
+ try:
133
+ cur.execute(f"select * from {table_name} where rownum=1")
134
+ except Exception as exc:
135
+ logger.info(f"Table not existing: {table_name}. Exception: {exc}")
136
+ return False
137
+ return True
138
+
139
+ def get_type(self, column: Series, dtype: str) -> str:
140
+ if dtype in (
141
+ PandasTypes.MIXED,
142
+ PandasTypes.UNKNOWN_ARRAY,
143
+ PandasTypes.COMPLEX,
144
+ ):
145
+ return 'CHAR(255)'
146
+ elif dtype in (PandasTypes.DATETIME, PandasTypes.DATETIME64):
147
+ try:
148
+ if column.dt.tz:
149
+ return 'TIMESTAMP'
150
+ except AttributeError:
151
+ pass
152
+ return 'TIMESTAMP'
153
+ elif dtype == PandasTypes.TIME:
154
+ try:
155
+ if column.dt.tz:
156
+ return 'TIMESTAMP'
157
+ except AttributeError:
158
+ pass
159
+ return 'TIMESTAMP'
160
+ elif dtype == PandasTypes.DATE:
161
+ return 'DATE'
162
+ elif dtype == PandasTypes.STRING:
163
+ return 'CHAR(255)'
164
+ elif dtype == PandasTypes.CATEGORICAL:
165
+ return 'CHAR(255)'
166
+ elif dtype == PandasTypes.BYTES:
167
+ return 'CHAR(255)'
168
+ elif dtype in (PandasTypes.FLOATING, PandasTypes.DECIMAL, PandasTypes.MIXED_INTEGER_FLOAT):
169
+ return 'NUMBER'
170
+ elif dtype == PandasTypes.INTEGER:
171
+ max_int, min_int = column.max(), column.min()
172
+ if np.int16(max_int) == max_int and np.int16(min_int) == min_int:
173
+ return 'NUMBER'
174
+ elif np.int32(max_int) == max_int and np.int32(min_int) == min_int:
175
+ return 'NUMBER'
176
+ else:
177
+ return 'NUMBER'
178
+ elif dtype == PandasTypes.BOOLEAN:
179
+ return 'CHAR(52)'
180
+ elif dtype in (PandasTypes.TIMEDELTA, PandasTypes.TIMEDELTA64, PandasTypes.PERIOD):
181
+ return 'NUMBER'
182
+ elif dtype == PandasTypes.EMPTY:
183
+ return 'CHAR(255)'
184
+ else:
185
+ print(f'Invalid datatype provided: {dtype}')
186
+
187
+ return 'CHAR(255)'
188
+
189
+ def export(
190
+ self,
191
+ df: DataFrame,
192
+ table_name: str = None,
193
+ if_exists: ExportWritePolicy = ExportWritePolicy.REPLACE,
194
+ **kwargs,
195
+ ) -> None:
196
+ super().export(
197
+ df,
198
+ **kwargs,
199
+ table_name=table_name,
200
+ if_exists=if_exists,
201
+ # Oracle cursor execute will automatically add a semicolon at the end of the query.
202
+ skip_semicolon_at_end=True
203
+ )
204
+
205
+ def upload_dataframe(
206
+ self,
207
+ cursor: Any,
208
+ df: DataFrame,
209
+ db_dtypes: List[str],
210
+ dtypes: List[str],
211
+ full_table_name: str,
212
+ buffer: Union[IO, None] = None,
213
+ **kwargs,
214
+ ) -> None:
215
+ def serialize_obj(val):
216
+ if type(val) is dict or type(val) is np.ndarray:
217
+ return simplejson.dumps(
218
+ val,
219
+ default=encode_complex,
220
+ ignore_nan=True,
221
+ )
222
+ elif type(val) is list and len(val) >= 1 and type(val[0]) is dict:
223
+ return simplejson.dumps(
224
+ val,
225
+ default=encode_complex,
226
+ ignore_nan=True,
227
+ )
228
+ return val
229
+
230
+ # Create values
231
+ df_ = df.copy()
232
+ columns = df_.columns
233
+ for col in columns:
234
+ dtype = df_[col].dtype
235
+ if dtype == PandasTypes.OBJECT:
236
+ df_[col] = df_[col].apply(lambda x: serialize_obj(x))
237
+ elif dtype in (
238
+ PandasTypes.MIXED,
239
+ PandasTypes.UNKNOWN_ARRAY,
240
+ PandasTypes.COMPLEX,
241
+ ):
242
+ df_[col] = df_[col].astype('string')
243
+
244
+ # Remove extraneous surrounding double quotes
245
+ # that get added while performing conversion to string.
246
+ df_[col] = df_[col].apply(lambda x: x.strip('"') if x and isinstance(x, str) else x)
247
+ df_.replace({np.NaN: None}, inplace=True)
248
+ values = []
249
+ for i in range(0, len(df_)):
250
+ values.append(tuple(df_.fillna('').values[i]))
251
+
252
+ # Create values placeholder
253
+ colmn_names = df.columns.tolist()
254
+ values_placeholder = ""
255
+ for i in range(0, len(colmn_names)):
256
+ values_placeholder += f':{str(i + 1)},'
257
+
258
+ insert_sql = f'INSERT INTO {full_table_name} VALUES({values_placeholder.rstrip(",")})'
259
+ cursor.executemany(insert_sql, values)