mammoth-cli 2.2.30__tar.gz → 2.2.31__tar.gz

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.
Files changed (257) hide show
  1. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/PKG-INFO +1 -1
  2. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +1 -1
  4. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +11 -1
  5. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/view.py +232 -50
  6. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/view_ops.py +53 -1
  7. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/errors/envelope.py +4 -0
  8. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/command_contract.py +9 -0
  9. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/pyproject.toml +1 -1
  10. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/view.yaml +18 -13
  11. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/.gitignore +0 -0
  12. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/LICENSE +0 -0
  13. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/README.md +0 -0
  14. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/__main__.py +0 -0
  15. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/app.py +0 -0
  16. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
  17. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  18. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  19. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  20. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  21. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  22. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  23. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  24. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  25. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  26. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  27. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  28. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  29. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  30. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  31. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
  32. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  33. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  34. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  35. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  36. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  37. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  38. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  39. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
  40. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
  41. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  42. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  43. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  44. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  45. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  46. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  47. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  48. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  49. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
  50. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  51. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  52. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  53. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  54. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  55. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  56. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  57. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  58. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  59. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  60. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  61. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  62. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  63. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  64. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  65. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
  66. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  67. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  68. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  69. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  70. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  71. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  72. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  73. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  74. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  75. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  76. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  77. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  78. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
  79. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  80. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  81. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  82. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  83. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  84. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  85. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  86. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  87. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  88. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  89. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  90. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  91. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/__init__.py +0 -0
  92. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/activity.py +0 -0
  93. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/addon.py +0 -0
  94. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/agent.py +0 -0
  95. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/ai.py +0 -0
  96. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/annotation.py +0 -0
  97. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/auth.py +0 -0
  98. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/automation.py +0 -0
  99. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/batch.py +0 -0
  100. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/billing.py +0 -0
  101. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/browse.py +0 -0
  102. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/calc.py +0 -0
  103. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/capability.py +0 -0
  104. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/client_app.py +0 -0
  105. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/completion.py +0 -0
  106. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/config.py +0 -0
  107. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/connector.py +0 -0
  108. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/context.py +0 -0
  109. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/dashboard.py +0 -0
  110. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/data_app.py +0 -0
  111. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/dataset.py +0 -0
  112. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/doctor.py +0 -0
  113. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/external_key.py +0 -0
  114. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/file.py +0 -0
  115. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/folder.py +0 -0
  116. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/job.py +0 -0
  117. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/link.py +0 -0
  118. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/notification.py +0 -0
  119. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/parameter.py +0 -0
  120. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/project.py +0 -0
  121. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/registry.py +0 -0
  122. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/report.py +0 -0
  123. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/schedule.py +0 -0
  124. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/schema.py +0 -0
  125. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/skill.py +0 -0
  126. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/snippet.py +0 -0
  127. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/support.py +0 -0
  128. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/template.py +0 -0
  129. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/trash.py +0 -0
  130. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/upgrade.py +0 -0
  131. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/user.py +0 -0
  132. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/view_profile.py +0 -0
  133. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/webhook.py +0 -0
  134. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/workflow.py +0 -0
  135. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/workspace.py +0 -0
  136. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/__init__.py +0 -0
  137. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/credentials.py +0 -0
  138. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/endpoint.py +0 -0
  139. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/profiles.py +0 -0
  140. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/resolver.py +0 -0
  141. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/contracts/__init__.py +0 -0
  142. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/contracts/auth.py +0 -0
  143. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/embed.py +0 -0
  144. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/errors/__init__.py +0 -0
  145. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/manifest/__init__.py +0 -0
  146. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/manifest/loader.py +0 -0
  147. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/messages/__init__.py +0 -0
  148. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/__init__.py +0 -0
  149. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/envelope.py +0 -0
  150. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/normalize.py +0 -0
  151. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/policy.py +0 -0
  152. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/progress.py +0 -0
  153. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/render.py +0 -0
  154. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/py.typed +0 -0
  155. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/__init__.py +0 -0
  156. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/confirm.py +0 -0
  157. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dataset_health.py +0 -0
  158. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dryrun.py +0 -0
  159. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dryrun_targets.py +0 -0
  160. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/embedded.py +0 -0
  161. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/executor.py +0 -0
  162. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/input_loader.py +0 -0
  163. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/intent_only.py +0 -0
  164. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/invocation.py +0 -0
  165. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/new_data.py +0 -0
  166. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/options.py +0 -0
  167. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/parents.py +0 -0
  168. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/runlog.py +0 -0
  169. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/session.py +0 -0
  170. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/state.py +0 -0
  171. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/strict.py +0 -0
  172. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/updates.py +0 -0
  173. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/validate.py +0 -0
  174. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/verify.py +0 -0
  175. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/__init__.py +0 -0
  176. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/argspec.py +0 -0
  177. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/board_values.py +0 -0
  178. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/coerce.py +0 -0
  179. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/conditional_format.py +0 -0
  180. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/conditions.py +0 -0
  181. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_filters.py +0 -0
  182. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_pages.py +0 -0
  183. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_review.py +0 -0
  184. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/data_profile.py +0 -0
  185. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/data_quality.py +0 -0
  186. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dispatch.py +0 -0
  187. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/factory.py +0 -0
  188. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/input_fields.py +0 -0
  189. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/listing.py +0 -0
  190. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/local_time.py +0 -0
  191. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/mapping.py +0 -0
  192. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/openapi_types.py +0 -0
  193. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/positionals.py +0 -0
  194. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/protocol.py +0 -0
  195. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/read_queries.py +0 -0
  196. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/sdk_service.py +0 -0
  197. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/sql_check.py +0 -0
  198. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/stored_stats.py +0 -0
  199. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/testing.py +0 -0
  200. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/text_dates.py +0 -0
  201. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/type_system.py +0 -0
  202. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/write_impact.py +0 -0
  203. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/skills/__init__.py +0 -0
  204. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/skills/installer.py +0 -0
  205. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/skills/steering.py +0 -0
  206. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/testing.py +0 -0
  207. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/activity.yaml +0 -0
  208. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/addon.yaml +0 -0
  209. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/agent.yaml +0 -0
  210. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/ai.yaml +0 -0
  211. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/annotation.yaml +0 -0
  212. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/auth.yaml +0 -0
  213. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/automation.yaml +0 -0
  214. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/batch.yaml +0 -0
  215. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/billing.yaml +0 -0
  216. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/browse.yaml +0 -0
  217. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/calc.yaml +0 -0
  218. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/capability.yaml +0 -0
  219. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/client-app.yaml +0 -0
  220. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/completion.yaml +0 -0
  221. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/config.yaml +0 -0
  222. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/connector.yaml +0 -0
  223. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/context.yaml +0 -0
  224. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/dashboard.yaml +0 -0
  225. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/data-app.yaml +0 -0
  226. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/dataset.yaml +0 -0
  227. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/doctor.yaml +0 -0
  228. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/external-key.yaml +0 -0
  229. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/file.yaml +0 -0
  230. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/folder.yaml +0 -0
  231. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/job.yaml +0 -0
  232. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/link.yaml +0 -0
  233. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/log.yaml +0 -0
  234. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/notification.yaml +0 -0
  235. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/parameter.yaml +0 -0
  236. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/project.yaml +0 -0
  237. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/report.yaml +0 -0
  238. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/schedule.yaml +0 -0
  239. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/schema.yaml +0 -0
  240. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/skill.yaml +0 -0
  241. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/snippet.yaml +0 -0
  242. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/support.yaml +0 -0
  243. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/template.yaml +0 -0
  244. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/trash.yaml +0 -0
  245. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/upgrade.yaml +0 -0
  246. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/user.yaml +0 -0
  247. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/version.yaml +0 -0
  248. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/webhook.yaml +0 -0
  249. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/workflow.yaml +0 -0
  250. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/workspace.yaml +0 -0
  251. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/edits-target.source.yaml +0 -0
  252. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/openapi-operations.yaml +0 -0
  253. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/schema-v1.json +0 -0
  254. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/sdk-catalog.source.yaml +0 -0
  255. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/sdk-methods.yaml +0 -0
  256. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/openapi/metadata.json +0 -0
  257. {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/openapi/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mammoth-cli
3
- Version: 2.2.30
3
+ Version: 2.2.31
4
4
  Summary: Command-line interface for the Mammoth Analytics platform
5
5
  Project-URL: homepage, https://mammoth.io
6
6
  Project-URL: repository, https://github.com/EdgeMetric/mammothsdk
@@ -6,7 +6,7 @@ not implement a second HTTP client and does not call private SDK members.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "2.2.30"
9
+ __version__ = "2.2.31"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -106,7 +106,7 @@ Checkpoint Alert are the `view data-check` and `view checkpoint` families.
106
106
  | Add columns from another view by matching keys | Join | `join` (`INNER`, `LEFT`, `RIGHT`, `OUTER`) |
107
107
  | Bring one value per key from another view | Lookup | `lookup` |
108
108
  | Totals, counts, averages per group | Group & Pivot | `pivot` (replaces the view's columns) |
109
- | A rows-by-columns summary table | Group & Pivot (crosstab) | `crosstab` (writes a new dataset) |
109
+ | A rows-by-columns summary table | Group & Pivot (crosstab) | `crosstab` (writes a new dataset, and keeps rewriting it on every pipeline run of the view) |
110
110
  | Turn wide columns into label/value rows | Unpivot | `unnest` |
111
111
  | Read fields out of a JSON column | Extract JSON | `json-extract` |
112
112
  | Classify, tag or summarise rows with AI | Generative AI | `ai` |
@@ -84,7 +84,7 @@ the run log. The connector command is `external_effect` with
84
84
  mammoth schema get view.export.postgres # read secret_fields, required fields
85
85
  # operator writes /private/path/request.json (mode 0600):
86
86
  # {"host":"db.example","port":5432,"database":"analytics","table":"exports",
87
- # "username":"agent","password":"…","dataset_id":DATASET_ID}
87
+ # "username":"agent","password":"…","replace_table":true,"dataset_id":DATASET_ID}
88
88
  mammoth view export postgres VIEW_ID DATASET_ID --project PROJECT_ID \
89
89
  --input /private/path/request.json --yes
90
90
  mammoth view export list VIEW_ID DATASET_ID --project PROJECT_ID
@@ -93,6 +93,16 @@ mammoth view export list VIEW_ID DATASET_ID --project PROJECT_ID
93
93
  If the operator has not supplied such a file, stop and ask for it; do not
94
94
  compose the body yourself from values seen in chat.
95
95
 
96
+ A database export (`postgres`, `mysql`, `mssql`, `redshift`, `bigquery` with
97
+ `export_type` REPLACE, and `publish-db`) drops and recreates its destination
98
+ table now and on every later run of the view's pipeline, so any existing table
99
+ of that name is lost. It is refused (`replace_table_not_acknowledged`) unless
100
+ the input says `"replace_table": true`; only set that for a table the user said
101
+ may be overwritten. `postgres` and `mysql` also cut a table name to 60
102
+ characters and write a different table, so a longer name is refused
103
+ (`table_name_too_long`). The result carries `refreshes_on_pipeline_run` and a
104
+ `note` saying so.
105
+
96
106
  ## Generic export routes
97
107
 
98
108
  `view export csv` and the typed destination commands are the proven path. The
@@ -2963,13 +2963,53 @@ def view_pipeline_rerun(invocation: Invocation) -> HandlerResult:
2963
2963
  service, invocation, dataview_id, document, 1
2964
2964
  )
2965
2965
  before = _rows_before(service, dataset_id, dataview_id, invocation.project)
2966
+ fired = _end_of_pipeline_exports(service, dataview_id, dataset_id)
2966
2967
  data = service.call(_symbol(invocation), **kwargs)
2967
2968
  data = _settle_async_view_write(
2968
2969
  service, dataset_id, dataview_id, invocation.project, data, before
2969
2970
  )
2971
+ if fired and isinstance(data, dict):
2972
+ data = {**data, "exports_fired": fired}
2970
2973
  return data, _meta(invocation, auth.workspace_id, None)
2971
2974
 
2972
2975
 
2976
+ #: Non-secret ``target_properties`` keys that say where an export writes.
2977
+ _EXPORT_TARGET_HINT_KEYS = ("table", "host", "database", "TARGET_DS_ID", "DS_NAME", "emails")
2978
+
2979
+
2980
+ def _end_of_pipeline_exports(
2981
+ service: Any, dataview_id: int, dataset_id: int | None
2982
+ ) -> list[dict[str, Any]]:
2983
+ """The live end-of-pipeline exports a rerun of this view fires again.
2984
+
2985
+ A rerun re-sends every one of them to its destination (database, email, REST
2986
+ endpoint, another dataset), so the caller is told which, not just that the
2987
+ pipeline ran. Soft-deleted exports do not fire.
2988
+ """
2989
+ listing = service.call(
2990
+ _EXPORTS_LIST_SYMBOL,
2991
+ dataview_id=dataview_id,
2992
+ dataset_id=dataset_id,
2993
+ end_of_pipeline=True,
2994
+ )
2995
+ fired: list[dict[str, Any]] = []
2996
+ for export in getattr(listing, "exports", None) or []:
2997
+ if export.status is ExportStatus.DELETED:
2998
+ continue
2999
+ handler = export.handler_type
3000
+ properties = export.target_properties or {}
3001
+ fired.append(
3002
+ {
3003
+ "export_id": export.id,
3004
+ "handler_type": getattr(handler, "value", handler),
3005
+ "target": {
3006
+ key: properties[key] for key in _EXPORT_TARGET_HINT_KEYS if key in properties
3007
+ },
3008
+ }
3009
+ )
3010
+ return fired
3011
+
3012
+
2973
3013
  #: Seconds ``view pipeline wait`` waits when the caller names no ``timeout`` (the SDK's own
2974
3014
  #: default is an hour, one request every few seconds for as long as it runs).
2975
3015
  _PIPELINE_WAIT_TIMEOUT = 300.0
@@ -3261,20 +3301,24 @@ def view_export_publish_db(invocation: Invocation) -> HandlerResult:
3261
3301
  document = invocation.load_input()
3262
3302
  odbc_type = _require_field(document, "odbc_type")
3263
3303
  target_properties = _require_field(document, "target_properties")
3304
+ assert document is not None
3305
+ table = target_properties.get("table") if isinstance(target_properties, dict) else None
3264
3306
  enforce_confirmation(
3265
3307
  invocation, policy=POLICY_YES_ALWAYS, action=f"publish-db on view {dataview_id}"
3266
3308
  )
3309
+ table_note = _reject_unacknowledged_table_replace(invocation.command_id, document, table)
3267
3310
  kwargs: dict[str, Any] = {
3268
3311
  "dataview_id": dataview_id,
3269
3312
  "odbc_type": odbc_type,
3270
3313
  "target_properties": target_properties,
3271
3314
  "project_id": project_id,
3272
3315
  }
3273
- assert document is not None
3274
3316
  _forward_optional(document, kwargs, ("dataset_id",))
3275
3317
  with open_service(invocation) as (service, auth):
3276
3318
  _require_exact_parent(service, invocation, dataview_id, kwargs)
3277
3319
  data = service.call(_symbol(invocation), **kwargs)
3320
+ if table_note is not None:
3321
+ data = _with_recurrence_note(data, table_note)
3278
3322
  return data, _meta(invocation, auth.workspace_id, project_id)
3279
3323
 
3280
3324
 
@@ -3453,6 +3497,81 @@ def _reject_export_into_draft(service: Any, dataview_id: int, dataset_id: int) -
3453
3497
  )
3454
3498
 
3455
3499
 
3500
+ # Database exports rebuild their destination table: the distributary drops it
3501
+ # and recreates it from the view on the first write and on every later pipeline
3502
+ # run (BigQuery only for export_type REPLACE, its WRITE_TRUNCATE mode). Postgres
3503
+ # and MySQL also cut a table name to 60 characters, silently writing elsewhere.
3504
+ _REPLACE_TABLE_FIELD = "replace_table"
3505
+ _DB_REPLACE_ROUTES = frozenset(
3506
+ {
3507
+ "view.export.postgres",
3508
+ "view.export.mysql",
3509
+ "view.export.mssql",
3510
+ "view.export.redshift",
3511
+ "view.export.bigquery",
3512
+ }
3513
+ )
3514
+ _DB_TABLE_NAME_LIMIT_ROUTES = frozenset({"view.export.postgres", "view.export.mysql"})
3515
+ _DB_TABLE_NAME_LIMIT = 60
3516
+
3517
+
3518
+ def _replaces_table(command_id: str, document: dict[str, Any]) -> bool:
3519
+ """Whether this database export drops and recreates its destination table."""
3520
+ if command_id == "view.export.bigquery":
3521
+ return str(document.get("export_type") or "REPLACE").upper() == "REPLACE"
3522
+ return command_id in _DB_REPLACE_ROUTES or command_id == "view.export.publish-db"
3523
+
3524
+
3525
+ def _reject_unacknowledged_table_replace(
3526
+ command_id: str, document: dict[str, Any], table: Any
3527
+ ) -> str | None:
3528
+ """Refuse a database export that is not explicitly allowed to replace its table.
3529
+
3530
+ Returns the recurrence note for the result, or ``None`` when the export
3531
+ does not replace a table.
3532
+ """
3533
+ if not _replaces_table(command_id, document):
3534
+ return None
3535
+ name = str(table)
3536
+ if command_id in _DB_TABLE_NAME_LIMIT_ROUTES and len(name) > _DB_TABLE_NAME_LIMIT:
3537
+ raise CliError(
3538
+ code="table_name_too_long",
3539
+ message=(
3540
+ f"Table name {name!r} is {len(name)} characters; the database export cuts "
3541
+ f"table names to {_DB_TABLE_NAME_LIMIT} and would write a different table "
3542
+ f"({name[:_DB_TABLE_NAME_LIMIT]!r})."
3543
+ ),
3544
+ exit_status=EXIT_USAGE,
3545
+ hint=f"Use a table name of at most {_DB_TABLE_NAME_LIMIT} characters.",
3546
+ details={"table": name, "length": len(name), "limit": _DB_TABLE_NAME_LIMIT},
3547
+ )
3548
+ if document.get(_REPLACE_TABLE_FIELD) is not True:
3549
+ raise CliError(
3550
+ code="replace_table_not_acknowledged",
3551
+ message=(
3552
+ f"This export drops and recreates table {name!r} in the destination database "
3553
+ "now and on every pipeline run of the view; any existing table of that name "
3554
+ "and its rows are lost."
3555
+ ),
3556
+ exit_status=EXIT_USAGE,
3557
+ hint=(
3558
+ f'Pick a table that may be overwritten, then add "{_REPLACE_TABLE_FIELD}": true '
3559
+ "to the input to confirm."
3560
+ ),
3561
+ details={"table": name, "field": _REPLACE_TABLE_FIELD},
3562
+ )
3563
+ return f"drops and recreates {name} now and on every pipeline run"
3564
+
3565
+
3566
+ def _with_recurrence_note(data: Any, note: str) -> dict[str, Any]:
3567
+ """The export result as a mapping carrying ``refreshes_on_pipeline_run`` and ``note``."""
3568
+ result = data.model_dump(mode="json") if hasattr(data, "model_dump") else data
3569
+ record = dict(result) if isinstance(result, dict) else {"result": result}
3570
+ record["refreshes_on_pipeline_run"] = True
3571
+ record["note"] = note
3572
+ return record
3573
+
3574
+
3456
3575
  def view_export_specialized(invocation: Invocation) -> HandlerResult:
3457
3576
  """Run one of the SDK's typed ``View.export`` destination helpers."""
3458
3577
  route = _SPECIAL_EXPORTS.get(invocation.command_id)
@@ -3493,6 +3612,8 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3493
3612
  )
3494
3613
  common_fields = _SPECIAL_EXPORT_COMMON_FIELDS if accepts_var_keyword else frozenset()
3495
3614
  allowed = explicit_fields | common_fields | {_DATASET_ID_FIELD}
3615
+ if invocation.command_id in _DB_REPLACE_ROUTES:
3616
+ allowed = allowed | {_REPLACE_TABLE_FIELD}
3496
3617
  unknown = sorted(set(document) - allowed)
3497
3618
  if unknown:
3498
3619
  raise CliError(
@@ -3520,8 +3641,12 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3520
3641
  policy=str(export_record.get("confirmation") or POLICY_YES_ALWAYS),
3521
3642
  action=f"export view {dataview_id} via {invocation.command_id.rsplit('.', 1)[-1]}",
3522
3643
  )
3644
+ table_note = _reject_unacknowledged_table_replace(
3645
+ invocation.command_id, document, document.get("table")
3646
+ )
3523
3647
  kwargs = dict(document)
3524
3648
  kwargs.pop(_DATASET_ID_FIELD, None)
3649
+ kwargs.pop(_REPLACE_TABLE_FIELD, None)
3525
3650
  is_dataset_route = invocation.command_id == "view.export.dataset"
3526
3651
  target_ds_id = kwargs.get("target_ds_id") if is_dataset_route else None
3527
3652
  save_as_mode = kwargs.get("save_as_mode") if is_dataset_route else None
@@ -3530,62 +3655,35 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3530
3655
  with open_service(invocation) as (service, auth):
3531
3656
  if dataset_id is None:
3532
3657
  dataset_id = _resolve_dataset_id(service, invocation, dataview_id, document)
3533
- if target_ds_id is not None and int(target_ds_id) == dataset_id:
3534
- raise CliError(
3535
- code=CODE_INVALID_ARGUMENTS,
3536
- message="target_ds_id equals the view's own dataset.",
3537
- exit_status=EXIT_USAGE,
3538
- hint=(
3539
- "A view cannot write into its own dataset. Export to a new "
3540
- "dataset name (omit target_ds_id), or target a different dataset."
3541
- ),
3542
- details={"dataset_id": dataset_id, "target_ds_id": int(target_ds_id)},
3543
- )
3544
3658
  if is_dataset_route:
3545
- _reject_export_into_draft(service, dataview_id, dataset_id)
3659
+ reject_dataset_export_conflicts(
3660
+ service,
3661
+ dataview_id,
3662
+ dataset_id,
3663
+ int(target_ds_id) if target_ds_id is not None else None,
3664
+ )
3546
3665
  target_view_before = None
3547
3666
  target_only_columns: list[str] = []
3548
3667
  mapped_type_warnings: list[str] = []
3668
+ hidden_left_out = (
3669
+ _hidden_column_names(service, dataset_id, dataview_id, project_id)
3670
+ if is_dataset_route and target_ds_id is None
3671
+ else []
3672
+ )
3549
3673
  if target_ds_id is not None:
3550
- # Each `view export dataset` call creates its own PERSISTENT
3551
- # export trigger on this view, which re-runs on every pipeline
3552
- # run; REPLACE_IN_DS/APPEND_TO_DS only ever touch that trigger's
3553
- # OWN rows. A second export from this view into the same target
3554
- # is therefore a second writer, not a refresh of the first --
3555
- # the fix is to rerun the pipeline, not to export again.
3556
- existing_export = _existing_internal_dataset_export(
3557
- service, dataview_id, dataset_id, int(target_ds_id)
3558
- )
3559
- if existing_export is not None:
3560
- raise CliError(
3561
- code="export_already_exists",
3562
- message=(
3563
- f"View {dataview_id} already exports into dataset "
3564
- f"{int(target_ds_id)} (export {existing_export.id}). This export "
3565
- "re-runs on every pipeline run, and REPLACE_IN_DS replaces only "
3566
- "its own rows -- a second export into the same target duplicates "
3567
- "rows instead of refreshing them."
3568
- ),
3569
- exit_status=EXIT_USAGE,
3570
- hint="Refresh the target by rerunning the pipeline, not by exporting again.",
3571
- recovery_commands=[f"mammoth view pipeline rerun {dataview_id}"],
3572
- details={
3573
- "dataview_id": dataview_id,
3574
- "target_ds_id": int(target_ds_id),
3575
- "export_id": existing_export.id,
3576
- },
3577
- )
3578
3674
  # rows_before is the target's own count ahead of this write, read
3579
3675
  # before the call so a later re-read can never be confused with it.
3580
3676
  target_view_before = _dataset_view_info(service, int(target_ds_id), result_project_id)
3581
- if save_as_mode == "APPEND_TO_DS":
3582
- target_only_columns, mapped_type_warnings = _reject_append_schema_mismatch(
3583
- service,
3584
- dataview_id,
3585
- dataset_id,
3586
- int(target_ds_id),
3587
- kwargs.get("column_mapping"),
3588
- )
3677
+ # REPLACE_IN_DS into an existing target runs the same backend
3678
+ # append code as APPEND_TO_DS (same mapping and schema handling),
3679
+ # so both modes get the same guards.
3680
+ target_only_columns, mapped_type_warnings = _reject_append_schema_mismatch(
3681
+ service,
3682
+ dataview_id,
3683
+ dataset_id,
3684
+ int(target_ds_id),
3685
+ kwargs.get("column_mapping"),
3686
+ )
3589
3687
  try:
3590
3688
  data = service.call_view(dataview_id, method, dataset_id=dataset_id, **kwargs)
3591
3689
  except CliError as error:
@@ -3642,6 +3740,11 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3642
3740
  # a non-increase here as unverified, not just an omitted count.
3643
3741
  row_check["expected_row_increase"] = True
3644
3742
  warnings = list(mapped_type_warnings)
3743
+ if hidden_left_out:
3744
+ warnings.append(
3745
+ "hidden column(s) of the source view are not in the new dataset: "
3746
+ + ", ".join(hidden_left_out)
3747
+ )
3645
3748
  if target_only_columns:
3646
3749
  # Allowed -- an append never has to cover every target column
3647
3750
  # -- but worth surfacing rather than leaving silent.
@@ -3655,6 +3758,8 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3655
3758
  data["row_check"] = row_check
3656
3759
  if pipeline_error is not None:
3657
3760
  data["pipeline_error"] = pipeline_error
3761
+ if table_note is not None:
3762
+ data = _with_recurrence_note(data, table_note)
3658
3763
  return data, _meta(invocation, auth.workspace_id, project_id)
3659
3764
 
3660
3765
 
@@ -3721,6 +3826,82 @@ def _existing_internal_dataset_export(
3721
3826
  return max(matches, key=lambda export: export.id or 0) if matches else None
3722
3827
 
3723
3828
 
3829
+ def _hidden_column_names(
3830
+ service: Any, dataset_id: int, dataview_id: int, project_id: int | None
3831
+ ) -> list[str]:
3832
+ """Display names of the view's hidden columns, which a new-dataset export leaves out."""
3833
+ info = apply_column_renames(
3834
+ service.call(
3835
+ _DATAVIEW_GET_SYMBOL,
3836
+ dataset_id=dataset_id,
3837
+ dataview_id=dataview_id,
3838
+ project_id=project_id,
3839
+ )
3840
+ )
3841
+ if not isinstance(info, dict):
3842
+ return []
3843
+ display = info.get("display_properties")
3844
+ hidden = display.get("HIDDEN_COLUMNS") if isinstance(display, dict) else None
3845
+ if not isinstance(hidden, list) or not hidden:
3846
+ return []
3847
+ names = {
3848
+ column.get(_INTERNAL_NAME_KEY): column.get(_DISPLAY_NAME_KEY) or column.get("name")
3849
+ for column in info.get(_METADATA_KEY) or []
3850
+ if isinstance(column, dict)
3851
+ }
3852
+ return [str(names.get(column) or column) for column in hidden]
3853
+
3854
+
3855
+ def reject_dataset_export_conflicts(
3856
+ service: Any, dataview_id: int, dataset_id: int, target_ds_id: int | None
3857
+ ) -> None:
3858
+ """Refuse a dataset export (or crosstab) that cannot write, or would double-write.
3859
+
3860
+ Not into the view's own dataset, not from a view holding its changes in a
3861
+ draft, and not into a target this view already exports into. Each dataset
3862
+ export creates its own PERSISTENT trigger on the view, which re-runs on
3863
+ every pipeline run; REPLACE_IN_DS/APPEND_TO_DS only touch that trigger's OWN
3864
+ rows, so a second export into the same target is a second writer, not a
3865
+ refresh -- the fix is to rerun the pipeline, not to export again.
3866
+ """
3867
+ if target_ds_id is not None and target_ds_id == dataset_id:
3868
+ raise CliError(
3869
+ code=CODE_INVALID_ARGUMENTS,
3870
+ message="target_ds_id equals the view's own dataset.",
3871
+ exit_status=EXIT_USAGE,
3872
+ hint=(
3873
+ "A view cannot write into its own dataset. Export to a new "
3874
+ "dataset name (omit target_ds_id), or target a different dataset."
3875
+ ),
3876
+ details={"dataset_id": dataset_id, "target_ds_id": target_ds_id},
3877
+ )
3878
+ _reject_export_into_draft(service, dataview_id, dataset_id)
3879
+ if target_ds_id is None:
3880
+ return
3881
+ existing_export = _existing_internal_dataset_export(
3882
+ service, dataview_id, dataset_id, target_ds_id
3883
+ )
3884
+ if existing_export is not None:
3885
+ raise CliError(
3886
+ code="export_already_exists",
3887
+ message=(
3888
+ f"View {dataview_id} already exports into dataset "
3889
+ f"{target_ds_id} (export {existing_export.id}). This export "
3890
+ "re-runs on every pipeline run, and REPLACE_IN_DS replaces only "
3891
+ "its own rows -- a second export into the same target duplicates "
3892
+ "rows instead of refreshing them."
3893
+ ),
3894
+ exit_status=EXIT_USAGE,
3895
+ hint="Refresh the target by rerunning the pipeline, not by exporting again.",
3896
+ recovery_commands=[f"mammoth view pipeline rerun {dataview_id}"],
3897
+ details={
3898
+ "dataview_id": dataview_id,
3899
+ "target_ds_id": target_ds_id,
3900
+ "export_id": existing_export.id,
3901
+ },
3902
+ )
3903
+
3904
+
3724
3905
  _DATASET_GET_SYMBOL = "mammoth.api.datasets.DatasetsAPI.get"
3725
3906
 
3726
3907
 
@@ -3874,7 +4055,8 @@ def _reject_append_schema_mismatch(
3874
4055
  target_ds_id: int,
3875
4056
  column_mapping: Any,
3876
4057
  ) -> tuple[list[str], list[str]]:
3877
- """Refuse an ``APPEND_TO_DS`` export whose source has a column the target
4058
+ """Refuse an ``APPEND_TO_DS``/``REPLACE_IN_DS`` export into an existing target
4059
+ whose source has a column the target
3878
4060
  dataset's schema does not, and ``column_mapping`` does not cover.
3879
4061
 
3880
4062
  A non-empty ``column_mapping`` is stricter: the backend then copies only
@@ -36,6 +36,7 @@ from mammoth_cli.commands.view import (
36
36
  join_after_snapshot,
37
37
  join_dry_run_preview,
38
38
  join_snapshot,
39
+ reject_dataset_export_conflicts,
39
40
  wait_for_followon_job,
40
41
  wait_for_pipeline_to_settle,
41
42
  wait_for_view_row_count,
@@ -230,6 +231,32 @@ def _resolve_exact_dataset_id(
230
231
  return value
231
232
 
232
233
 
234
+ def _require_one_destination(document: dict[str, Any], command_id: str) -> None:
235
+ """Refuse a transform that names both ``new_column`` and ``existing_column``, or neither.
236
+
237
+ The SDK silently keeps only ``new_column`` when both are given, and a step
238
+ with neither is saved and then fails in the worker ("No destination for
239
+ operation") after the pipeline has already been changed.
240
+ """
241
+ given = [name for name in ("new_column", "existing_column") if document.get(name) is not None]
242
+ if len(given) == 1:
243
+ return
244
+ command = command_id.replace(".", " ")
245
+ raise CliError(
246
+ code=CODE_INVALID_ARGUMENTS,
247
+ message=(
248
+ f"'{command}' needs exactly one of new_column or existing_column, "
249
+ + ("not both: existing_column would be ignored." if given else "but got neither.")
250
+ ),
251
+ exit_status=EXIT_USAGE,
252
+ hint=(
253
+ "Set new_column to write a new column, or existing_column to overwrite "
254
+ "one that is already in the view."
255
+ ),
256
+ details={"given": given},
257
+ )
258
+
259
+
233
260
  def _require_field(document: dict[str, Any] | None, field: str) -> Any:
234
261
  """Return a required field from the ``--input`` document, or raise usage."""
235
262
  if document is None or field not in document:
@@ -979,6 +1006,7 @@ def view_transform_combine_columns(invocation: Invocation) -> HandlerResult:
979
1006
  document = invocation.load_input()
980
1007
  _require_field(document, "sources")
981
1008
  assert document is not None
1009
+ _require_one_destination(document, invocation.command_id)
982
1010
  kwargs = _bind_transform_inputs(invocation, document)
983
1011
  return _dispatch_view(invocation, view_id, "combine_columns", **kwargs)
984
1012
 
@@ -1063,7 +1091,27 @@ def view_transform_crosstab(invocation: Invocation) -> HandlerResult:
1063
1091
  _require_field(document, "dataset_name")
1064
1092
  assert document is not None
1065
1093
  kwargs = _bind_transform_inputs(invocation, document)
1066
- return _dispatch_view(invocation, view_id, "crosstab", **kwargs)
1094
+
1095
+ def guard_target(service: Any, dataset_id: int, call_kwargs: dict[str, Any]) -> None:
1096
+ target = call_kwargs.get("target_ds_id")
1097
+ reject_dataset_export_conflicts(
1098
+ service, view_id, dataset_id, int(target) if target is not None else None
1099
+ )
1100
+
1101
+ def name_persistence(_service: Any, _dataset_id: int, _state: Any, data: Any) -> Any:
1102
+ return {
1103
+ "dataset_id": data,
1104
+ "source_view_id": view_id,
1105
+ "refreshes_on_pipeline_run": True,
1106
+ "note": (
1107
+ f"This crosstab is a standing export on view {view_id}: it rewrites dataset "
1108
+ f"{data} on every pipeline run of that view, not just now."
1109
+ ),
1110
+ }
1111
+
1112
+ return _dispatch_view(
1113
+ invocation, view_id, "crosstab", prepare=guard_target, after=name_persistence, **kwargs
1114
+ )
1067
1115
 
1068
1116
 
1069
1117
  def view_transform_date_diff(invocation: Invocation) -> HandlerResult:
@@ -1325,6 +1373,7 @@ def view_transform_lookup(invocation: Invocation) -> HandlerResult:
1325
1373
  _require_field(document, "key")
1326
1374
  _require_field(document, "value")
1327
1375
  assert document is not None
1376
+ _require_one_destination(document, invocation.command_id)
1328
1377
  kwargs = _bind_transform_inputs(invocation, document)
1329
1378
  return _dispatch_view(invocation, view_id, "lookup", **kwargs)
1330
1379
 
@@ -1335,6 +1384,7 @@ def view_transform_math(invocation: Invocation) -> HandlerResult:
1335
1384
  document = invocation.load_input()
1336
1385
  _require_field(document, "expression")
1337
1386
  assert document is not None
1387
+ _require_one_destination(document, invocation.command_id)
1338
1388
  kwargs = _bind_transform_inputs(invocation, document)
1339
1389
  return _dispatch_view(invocation, view_id, "math", **kwargs)
1340
1390
 
@@ -1369,6 +1419,7 @@ def view_transform_set_values(invocation: Invocation) -> HandlerResult:
1369
1419
  document = invocation.load_input()
1370
1420
  _require_field(document, "values")
1371
1421
  assert document is not None
1422
+ _require_one_destination(document, invocation.command_id)
1372
1423
  kwargs = _bind_transform_inputs(invocation, document)
1373
1424
  return _dispatch_view(invocation, view_id, "set_values", **kwargs)
1374
1425
 
@@ -1432,5 +1483,6 @@ def view_transform_window(invocation: Invocation) -> HandlerResult:
1432
1483
  document = invocation.load_input()
1433
1484
  _require_field(document, "function")
1434
1485
  assert document is not None
1486
+ _require_one_destination(document, invocation.command_id)
1435
1487
  kwargs = _bind_transform_inputs(invocation, document)
1436
1488
  return _dispatch_view(invocation, view_id, "window", **kwargs)
@@ -154,12 +154,16 @@ ERROR_SUMMARIES: dict[str, str] = {
154
154
  "project_required": "No project has been chosen for this step.",
155
155
  "pypi_response_invalid": "An update check didn't return a usable answer.",
156
156
  "pypi_unreachable": "An update check couldn't connect.",
157
+ "replace_table_not_acknowledged": (
158
+ "This export would drop and recreate a database table, and needs an explicit yes."
159
+ ),
157
160
  "resource_identity_required": "The assistant needs to say which item it means.",
158
161
  "resource_not_found": _S_NOT_FOUND,
159
162
  "retryable_error": "Mammoth was temporarily unavailable. Trying again may work.",
160
163
  "schema_not_found": _S_NOT_FOUND,
161
164
  "sdk_symbol_unresolved": _S_INTERNAL,
162
165
  "skill_conflict": _S_SETUP,
166
+ "table_name_too_long": "The database would cut this table name and write a different table.",
163
167
  "task_runtime_error": "A background task hit an error while running.",
164
168
  "timeout": "This step took too long. It may still be running.",
165
169
  "too_many_goals": _S_BAD_REQUEST,
@@ -734,6 +734,15 @@ _S7_ADDITIONAL_INPUT_FIELDS: dict[str, tuple[FieldSpec, ...]] = {
734
734
  "dashboard.bi-export": (
735
735
  FieldSpec("target", required=True, annotation=Literal["powerbi", "tableau"]),
736
736
  ),
737
+ # A database export drops and recreates its destination table, now and on
738
+ # every pipeline run; ``replace_table`` is the CLI-side acknowledgement
739
+ # (never forwarded to the SDK call, so its signature cannot introspect it).
740
+ **{
741
+ f"view.export.{route}": (
742
+ FieldSpec("replace_table", required=False, annotation=bool, default=False),
743
+ )
744
+ for route in ("postgres", "mysql", "mssql", "redshift", "bigquery", "publish-db")
745
+ },
737
746
  # user.update previously pinned name/email here as a guess, from when
738
747
  # UserProfileAPI.update took **fields and so had nothing introspectable.
739
748
  # The SDK method is now typed (first_name, last_name -- the backend's
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mammoth-cli"
3
- version = "2.2.30"
3
+ version = "2.2.31"
4
4
  description = "Command-line interface for the Mammoth Analytics platform"
5
5
  authors = [{name = "Mammoth Analytics", email = "support@mammoth.io"}]
6
6
  readme = "README.md"