mammoth-cli 2.0.82__tar.gz → 2.0.83__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 (236) hide show
  1. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/PKG-INFO +2 -2
  2. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/app.py +3 -1
  4. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +2 -2
  5. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +10 -0
  6. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/dataset.py +12 -0
  7. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/registry.py +1 -0
  8. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/schema.py +18 -1
  9. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/view.py +68 -13
  10. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/view_ops.py +26 -7
  11. mammoth_cli-2.0.83/mammoth_cli/runtime/dataset_health.py +62 -0
  12. mammoth_cli-2.0.83/mammoth_cli/runtime/new_data.py +86 -0
  13. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/verify.py +10 -2
  14. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/conditions.py +5 -0
  15. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/data_quality.py +82 -1
  16. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/pyproject.toml +2 -2
  17. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/connector.yaml +6 -5
  18. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/dataset.yaml +39 -0
  19. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/sdk-catalog.source.yaml +7 -0
  20. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/sdk-methods.yaml +16 -2
  21. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/.gitignore +0 -0
  22. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/LICENSE +0 -0
  23. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/README.md +0 -0
  24. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/__main__.py +0 -0
  25. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
  26. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
  27. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  28. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  29. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  30. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  31. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  32. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  33. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  34. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  35. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  36. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  37. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  38. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  39. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  40. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
  41. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  42. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  43. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  44. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  45. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  46. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  47. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  48. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  49. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  50. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  51. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  52. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  53. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  54. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  55. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  56. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  57. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  58. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  59. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  60. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  61. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  62. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  63. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  64. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  65. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  66. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  67. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  68. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  69. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  70. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
  71. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  72. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  73. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  74. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  75. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  76. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  77. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  78. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  79. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  80. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  81. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  82. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  83. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  84. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  85. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  86. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  87. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  88. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  89. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  90. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  91. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  92. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  93. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  94. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  95. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  96. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/__init__.py +0 -0
  97. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/activity.py +0 -0
  98. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/addon.py +0 -0
  99. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/agent.py +0 -0
  100. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/ai.py +0 -0
  101. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/annotation.py +0 -0
  102. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/auth.py +0 -0
  103. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/automation.py +0 -0
  104. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/batch.py +0 -0
  105. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/billing.py +0 -0
  106. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/browse.py +0 -0
  107. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/calc.py +0 -0
  108. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/capability.py +0 -0
  109. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/client_app.py +0 -0
  110. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/completion.py +0 -0
  111. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/config.py +0 -0
  112. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/connector.py +0 -0
  113. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/context.py +0 -0
  114. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/dashboard.py +0 -0
  115. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/data_app.py +0 -0
  116. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/doctor.py +0 -0
  117. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/external_key.py +0 -0
  118. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/file.py +0 -0
  119. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/folder.py +0 -0
  120. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/job.py +0 -0
  121. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/notification.py +0 -0
  122. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/parameter.py +0 -0
  123. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/project.py +0 -0
  124. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/report.py +0 -0
  125. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/schedule.py +0 -0
  126. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/skill.py +0 -0
  127. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/snippet.py +0 -0
  128. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/support.py +0 -0
  129. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/template.py +0 -0
  130. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/trash.py +0 -0
  131. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/upgrade.py +0 -0
  132. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/user.py +0 -0
  133. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/webhook.py +0 -0
  134. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/workflow.py +0 -0
  135. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/commands/workspace.py +0 -0
  136. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/context/__init__.py +0 -0
  137. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/context/credentials.py +0 -0
  138. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/context/endpoint.py +0 -0
  139. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/context/profiles.py +0 -0
  140. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/context/resolver.py +0 -0
  141. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/contracts/__init__.py +0 -0
  142. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/contracts/auth.py +0 -0
  143. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/embed.py +0 -0
  144. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/errors/__init__.py +0 -0
  145. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/errors/envelope.py +0 -0
  146. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/manifest/__init__.py +0 -0
  147. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/manifest/loader.py +0 -0
  148. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/messages/__init__.py +0 -0
  149. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/output/__init__.py +0 -0
  150. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/output/envelope.py +0 -0
  151. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/output/normalize.py +0 -0
  152. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/output/policy.py +0 -0
  153. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/output/progress.py +0 -0
  154. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/output/render.py +0 -0
  155. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/py.typed +0 -0
  156. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/__init__.py +0 -0
  157. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/confirm.py +0 -0
  158. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/dryrun.py +0 -0
  159. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/embedded.py +0 -0
  160. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/executor.py +0 -0
  161. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/input_loader.py +0 -0
  162. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/invocation.py +0 -0
  163. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/options.py +0 -0
  164. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/parents.py +0 -0
  165. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/runlog.py +0 -0
  166. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/session.py +0 -0
  167. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/state.py +0 -0
  168. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/strict.py +0 -0
  169. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/updates.py +0 -0
  170. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/runtime/validate.py +0 -0
  171. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/__init__.py +0 -0
  172. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/argspec.py +0 -0
  173. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/coerce.py +0 -0
  174. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/command_contract.py +0 -0
  175. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/dashboard_pages.py +0 -0
  176. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/dashboard_review.py +0 -0
  177. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/dispatch.py +0 -0
  178. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/factory.py +0 -0
  179. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/input_fields.py +0 -0
  180. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/mapping.py +0 -0
  181. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/openapi_types.py +0 -0
  182. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/positionals.py +0 -0
  183. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/protocol.py +0 -0
  184. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/sdk_service.py +0 -0
  185. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/testing.py +0 -0
  186. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/services/type_system.py +0 -0
  187. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/skills/__init__.py +0 -0
  188. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/skills/installer.py +0 -0
  189. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/skills/steering.py +0 -0
  190. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/mammoth_cli/testing.py +0 -0
  191. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/activity.yaml +0 -0
  192. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/addon.yaml +0 -0
  193. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/agent.yaml +0 -0
  194. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/ai.yaml +0 -0
  195. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/annotation.yaml +0 -0
  196. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/auth.yaml +0 -0
  197. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/automation.yaml +0 -0
  198. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/batch.yaml +0 -0
  199. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/billing.yaml +0 -0
  200. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/browse.yaml +0 -0
  201. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/calc.yaml +0 -0
  202. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/capability.yaml +0 -0
  203. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/client-app.yaml +0 -0
  204. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/completion.yaml +0 -0
  205. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/config.yaml +0 -0
  206. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/context.yaml +0 -0
  207. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/dashboard.yaml +0 -0
  208. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/data-app.yaml +0 -0
  209. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/doctor.yaml +0 -0
  210. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/external-key.yaml +0 -0
  211. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/file.yaml +0 -0
  212. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/folder.yaml +0 -0
  213. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/job.yaml +0 -0
  214. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/log.yaml +0 -0
  215. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/notification.yaml +0 -0
  216. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/parameter.yaml +0 -0
  217. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/project.yaml +0 -0
  218. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/report.yaml +0 -0
  219. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/schedule.yaml +0 -0
  220. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/schema.yaml +0 -0
  221. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/skill.yaml +0 -0
  222. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/snippet.yaml +0 -0
  223. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/support.yaml +0 -0
  224. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/template.yaml +0 -0
  225. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/trash.yaml +0 -0
  226. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/upgrade.yaml +0 -0
  227. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/user.yaml +0 -0
  228. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/version.yaml +0 -0
  229. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/view.yaml +0 -0
  230. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/webhook.yaml +0 -0
  231. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/workflow.yaml +0 -0
  232. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/commands/workspace.yaml +0 -0
  233. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/openapi-operations.yaml +0 -0
  234. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/manifests/schema-v1.json +0 -0
  235. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/openapi/metadata.json +0 -0
  236. {mammoth_cli-2.0.82 → mammoth_cli-2.0.83}/spec/openapi/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mammoth-cli
3
- Version: 2.0.82
3
+ Version: 2.0.83
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
@@ -17,7 +17,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
17
  Requires-Python: <3.15,>=3.12
18
18
  Requires-Dist: jsonschema>=4.20
19
19
  Requires-Dist: keyring<26,>=25.7
20
- Requires-Dist: mammoth-io<0.8,>=0.7.36
20
+ Requires-Dist: mammoth-io<0.8,>=0.7.37
21
21
  Requires-Dist: platformdirs<5,>=4.10
22
22
  Requires-Dist: pydantic<3,>=2.12.5
23
23
  Requires-Dist: pyyaml<7,>=6
@@ -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.0.82"
9
+ __version__ = "2.0.83"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -42,6 +42,8 @@ from mammoth_cli.output.policy import (
42
42
  resolve_output,
43
43
  )
44
44
  from mammoth_cli.runtime import executor, validate
45
+ from mammoth_cli.runtime.dataset_health import with_dataset_health
46
+ from mammoth_cli.runtime.new_data import with_new_data_path
45
47
  from mammoth_cli.runtime.invocation import Invocation
46
48
  from mammoth_cli.runtime.state import with_state
47
49
  from mammoth_cli.runtime.strict import validate_extra_args
@@ -826,7 +828,7 @@ def _execute(invocation: Invocation) -> None:
826
828
  if invocation.dry_run:
827
829
  return _dry_run(handler, invocation)
828
830
  data, meta = handler(invocation)
829
- return _apply_verify(invocation, data), meta
831
+ return with_new_data_path(with_dataset_health(_apply_verify(invocation, data))), meta
830
832
 
831
833
  executor.run(
832
834
  invocation.command_id,
@@ -1,6 +1,6 @@
1
1
  # Published CLI command catalog
2
2
 
3
- This generated catalog is a lookup table for every command in the published CLI manifest, currently 573; each entry carries its release status line. It is a command-contract index, not an assertion that every backend/OpenAPI operation has a CLI binding. For an exact local CLI contract, run `mammoth schema get COMMAND_ID`; `mammoth capability list` is an API-binding inventory and can omit typed/local CLI routes. For focused workflows, read the [recipes index](recipes/index.md). Sensitive structured input must come from a private file or pipe; never put secrets in literal argv.
3
+ This generated catalog is a lookup table for every command in the published CLI manifest, currently 574; each entry carries its release status line. It is a command-contract index, not an assertion that every backend/OpenAPI operation has a CLI binding. For an exact local CLI contract, run `mammoth schema get COMMAND_ID`; `mammoth capability list` is an API-binding inventory and can omit typed/local CLI routes. For focused workflows, read the [recipes index](recipes/index.md). Sensitive structured input must come from a private file or pipe; never put secrets in literal argv.
4
4
 
5
5
  Load only the applicable domain file:
6
6
 
@@ -24,7 +24,7 @@ Load only the applicable domain file:
24
24
  - [dashboard-qa](commands/dashboard-qa.md) — 13 published commands
25
25
  - [dashboard](commands/dashboard.md) — 100 published commands
26
26
  - [data-app](commands/data-app.md) — 12 published commands
27
- - [dataset](commands/dataset.md) — 17 published commands
27
+ - [dataset](commands/dataset.md) — 18 published commands
28
28
  - [doctor](commands/doctor.md) — 1 published commands
29
29
  - [external-key](commands/external-key.md) — 4 published commands
30
30
  - [file](commands/file.md) — 9 published commands
@@ -12,6 +12,16 @@ Result: `DatasetBatchDataResult`; mutation `read`, confirmation `none`, wait pol
12
12
 
13
13
  Status on release: ran once on CLI 1.1.11 — Published PyPI CLI 1.1.11 / SDK 0.7.1 batch-data read succeeded for an ID observed from retained dataset 29; bounded 50-row page. One observed-ID chain and page; not Full.
14
14
 
15
+ ### `dataset.broken-rows.list`
16
+
17
+ Run: `mammoth dataset broken-rows list`. Exact input fields: `mammoth schema get dataset.broken-rows.list`.
18
+
19
+ Example: `mammoth dataset broken-rows list 123`. Placeholders are illustrative; resolve IDs and input from observed reads.
20
+
21
+ Result: `DatasetBrokenRowsListResult`; mutation `read`, confirmation `none`, wait policy `not_async`.
22
+
23
+ Status on release: untried; no live run recorded.
24
+
15
25
  ### `dataset.bulk-delete`
16
26
 
17
27
  Run: `mammoth dataset bulk-delete`. Exact input fields: `mammoth schema get dataset.bulk-delete`.
@@ -28,6 +28,7 @@ from mammoth_cli.runtime.confirm import (
28
28
  enforce_confirmation,
29
29
  )
30
30
  from mammoth_cli.runtime.invocation import Invocation
31
+ from mammoth_cli.runtime.new_data import with_file_upload_path
31
32
  from mammoth_cli.runtime.session import open_service, require_project
32
33
 
33
34
  HandlerResult = tuple[Any, dict[str, Any]]
@@ -322,6 +323,17 @@ def dataset_batch_data(invocation: Invocation) -> HandlerResult:
322
323
 
323
324
  def dataset_file_settings(invocation: Invocation) -> HandlerResult:
324
325
  """Get file settings (delimiter, header, dates, ...) for a dataset."""
326
+ data, meta = _read_dataset(invocation)
327
+ return with_file_upload_path(data, _require_int_positional(invocation, "dataset id")), meta
328
+
329
+
330
+ def dataset_broken_rows(invocation: Invocation) -> HandlerResult:
331
+ """List the lines of a dataset's uploaded file that could not be parsed."""
332
+ return _read_dataset(invocation)
333
+
334
+
335
+ def _read_dataset(invocation: Invocation) -> HandlerResult:
336
+ """Call this command's SDK read with the DATASET_ID positional."""
325
337
  project_id = require_project(invocation)
326
338
  dataset_id = _require_int_positional(invocation, "dataset id")
327
339
  if dataset_id <= 0:
@@ -314,6 +314,7 @@ HANDLERS: dict[str, Handler] = {
314
314
  "dataset.file-settings.get": dataset_cmd.dataset_file_settings,
315
315
  "dataset.file-settings.update": dataset_cmd.dataset_file_settings_update,
316
316
  "dataset.file-settings.undo": dataset_cmd.dataset_file_settings_undo,
317
+ "dataset.broken-rows.list": dataset_cmd.dataset_broken_rows,
317
318
  "dataset.create": dataset_cmd.dataset_create,
318
319
  "dataset.create-from-pdf": dataset_cmd.dataset_create_from_pdf,
319
320
  "dataset.rename": dataset_cmd.dataset_rename,
@@ -1402,7 +1402,9 @@ def _schema_common(record: dict[str, Any]) -> dict[str, Any]:
1402
1402
  contract_level = (
1403
1403
  "opaque_expert"
1404
1404
  if record["command_id"] in _OPAQUE_EXPERT_COMMANDS
1405
- else "partially_typed" if opaque_fields else "typed"
1405
+ else "partially_typed"
1406
+ if opaque_fields
1407
+ else "typed"
1406
1408
  )
1407
1409
  return {
1408
1410
  "positionals": _positionals(record["command_id"]),
@@ -1558,6 +1560,13 @@ def _inline_call_detail(entries: list[dict[str, Any]]) -> None:
1558
1560
  entry["agent_example"] = record["agent_example"]
1559
1561
 
1560
1562
 
1563
+ #: How to drill down from a find: a family's full command list, or every family.
1564
+ _BROWSE_NEXT = (
1565
+ "mammoth schema list FAMILY lists every command in a family; mammoth schema list "
1566
+ "lists the families"
1567
+ )
1568
+
1569
+
1561
1570
  def find_schemas(
1562
1571
  query: str,
1563
1572
  *,
@@ -1747,9 +1756,17 @@ def find_schemas(
1747
1756
  if result["suggestions"]
1748
1757
  else _NO_MATCH_HINT_NO_SUGGESTIONS
1749
1758
  )
1759
+ result["browse"] = _browse(page or result.get("suggestions", []))
1750
1760
  return result
1751
1761
 
1752
1762
 
1763
+ def _browse(entries: list[dict[str, Any]]) -> dict[str, Any]:
1764
+ """The families behind *entries* and how to list each one in full: a keyword
1765
+ find surfaces one way to do a thing, its family holds the siblings."""
1766
+ families = list(dict.fromkeys(entry["command_path"].split()[0] for entry in entries))
1767
+ return {"families": families, "next": _BROWSE_NEXT}
1768
+
1769
+
1753
1770
  def get_schema(command_id: str) -> dict[str, Any] | None:
1754
1771
  record = command_by_id(command_id)
1755
1772
  if record is None or record.get("disposition") == "alias":
@@ -2073,13 +2073,16 @@ def view_pipeline_rerun(invocation: Invocation) -> HandlerResult:
2073
2073
  kwargs: dict[str, Any] = {"dataview_id": dataview_id}
2074
2074
  _forward_optional(document, kwargs, ("from_sequence", "dataset_id"))
2075
2075
  with open_service(invocation) as (service, auth):
2076
- data = service.call(_symbol(invocation), **kwargs)
2077
2076
  dataset_id = kwargs.get("dataset_id")
2078
2077
  if dataset_id is None:
2079
2078
  dataset_id = _resolve_dataset_id_for_settle(
2080
2079
  service, invocation, dataview_id, document, 1
2081
2080
  )
2082
- data = _settle_async_view_write(service, dataset_id, dataview_id, invocation.project, data)
2081
+ before = _rows_before(service, dataset_id, dataview_id, invocation.project)
2082
+ data = service.call(_symbol(invocation), **kwargs)
2083
+ data = _settle_async_view_write(
2084
+ service, dataset_id, dataview_id, invocation.project, data, before
2085
+ )
2083
2086
  return data, _meta(invocation, auth.workspace_id, None)
2084
2087
 
2085
2088
 
@@ -2133,13 +2136,16 @@ def view_task_delete(invocation: Invocation) -> HandlerResult:
2133
2136
  kwargs: dict[str, Any] = {"dataview_id": dataview_id, "task_id": task_id}
2134
2137
  _forward_optional(document, kwargs, ("dataset_id",))
2135
2138
  with open_service(invocation) as (service, auth):
2136
- data = service.call(_symbol(invocation), **kwargs)
2137
2139
  dataset_id = kwargs.get("dataset_id")
2138
2140
  if dataset_id is None:
2139
2141
  dataset_id = _resolve_dataset_id_for_settle(
2140
2142
  service, invocation, dataview_id, document, 2
2141
2143
  )
2142
- data = _settle_async_view_write(service, dataset_id, dataview_id, invocation.project, data)
2144
+ before = _rows_before(service, dataset_id, dataview_id, invocation.project)
2145
+ data = service.call(_symbol(invocation), **kwargs)
2146
+ data = _settle_async_view_write(
2147
+ service, dataset_id, dataview_id, invocation.project, data, before
2148
+ )
2143
2149
  return data, _meta(invocation, auth.workspace_id, None)
2144
2150
 
2145
2151
 
@@ -2193,13 +2199,16 @@ def view_task_update(invocation: Invocation) -> HandlerResult:
2193
2199
  assert document is not None
2194
2200
  _forward_optional(document, kwargs, ("dataset_id",))
2195
2201
  with open_service(invocation) as (service, auth):
2196
- data = service.call(_symbol(invocation), **kwargs)
2197
2202
  dataset_id = kwargs.get("dataset_id")
2198
2203
  if dataset_id is None:
2199
2204
  dataset_id = _resolve_dataset_id_for_settle(
2200
2205
  service, invocation, dataview_id, document, 2
2201
2206
  )
2202
- data = _settle_async_view_write(service, dataset_id, dataview_id, invocation.project, data)
2207
+ before = _rows_before(service, dataset_id, dataview_id, invocation.project)
2208
+ data = service.call(_symbol(invocation), **kwargs)
2209
+ data = _settle_async_view_write(
2210
+ service, dataset_id, dataview_id, invocation.project, data, before
2211
+ )
2203
2212
  return data, _meta(invocation, auth.workspace_id, None)
2204
2213
 
2205
2214
 
@@ -2509,6 +2518,33 @@ _SPECIAL_EXPORT_COMMON_FIELDS = frozenset(
2509
2518
  )
2510
2519
 
2511
2520
 
2521
+ def _reject_export_into_draft(service: Any, dataview_id: int, dataset_id: int) -> None:
2522
+ """Refuse a dataset export on a view whose changes are held in a draft.
2523
+
2524
+ With auto-run off the view keeps new pipeline changes in a draft, so the
2525
+ export would only be staged and write nothing; the SDK then waits out its
2526
+ whole timeout for a write that cannot happen (eval T1-O-11).
2527
+ """
2528
+ pipeline = service.call(_PIPELINE_GET_SYMBOL, dataview_id=dataview_id, dataset_id=dataset_id)
2529
+ draft_mode = pipeline.get("draft_mode") if isinstance(pipeline, dict) else None
2530
+ if draft_mode in (None, "off"):
2531
+ return
2532
+ raise CliError(
2533
+ code="view_in_draft",
2534
+ message=(
2535
+ f"View {dataview_id} holds its changes in a draft (auto-run is off), so an "
2536
+ "export added now would be staged and write nothing."
2537
+ ),
2538
+ exit_status=EXIT_USAGE,
2539
+ hint=(
2540
+ f"Turn auto-run on with mammoth view draft auto-run {dataview_id} --input "
2541
+ "'{\"enabled\": true}', add the export, then turn auto-run off again if the "
2542
+ f"dataset should only update when asked (mammoth view pipeline rerun {dataview_id})."
2543
+ ),
2544
+ details={"dataview_id": dataview_id, "draft_mode": draft_mode},
2545
+ )
2546
+
2547
+
2512
2548
  def view_export_specialized(invocation: Invocation) -> HandlerResult:
2513
2549
  """Run one of the SDK's typed ``View.export`` destination helpers."""
2514
2550
  route = _SPECIAL_EXPORTS.get(invocation.command_id)
@@ -2596,6 +2632,8 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
2596
2632
  ),
2597
2633
  details={"dataset_id": dataset_id, "target_ds_id": int(target_ds_id)},
2598
2634
  )
2635
+ if is_dataset_route:
2636
+ _reject_export_into_draft(service, dataview_id, dataset_id)
2599
2637
  target_view_before = None
2600
2638
  target_only_columns: list[str] = []
2601
2639
  if target_ds_id is not None:
@@ -2999,8 +3037,30 @@ def _row_count_now(service: Any, dataset_id: int, view_id: int, project_id: int
2999
3037
  return info.get("row_count") if isinstance(info, dict) else None
3000
3038
 
3001
3039
 
3040
+ def _rows_before(
3041
+ service: Any, dataset_id: Any, view_id: int, project_id: int | None
3042
+ ) -> dict[str, Any] | None:
3043
+ """The view's row count read BEFORE an async write is sent.
3044
+
3045
+ Read after the call, a small view may already have recomputed, so the
3046
+ "before" would be the new count and a real change would look like none.
3047
+ None when the parent dataset is unknown (the settle step is skipped then).
3048
+ """
3049
+ if dataset_id is None:
3050
+ return None
3051
+ try:
3052
+ return {"rows_before": _row_count_now(service, int(dataset_id), view_id, project_id)}
3053
+ except CliError as exc:
3054
+ return {"rows_before": None, "rows_before_error": f"{type(exc).__name__}: {exc}"}
3055
+
3056
+
3002
3057
  def _settle_async_view_write(
3003
- service: Any, dataset_id: Any, view_id: int, project_id: int | None, data: Any
3058
+ service: Any,
3059
+ dataset_id: Any,
3060
+ view_id: int,
3061
+ project_id: int | None,
3062
+ data: Any,
3063
+ before: dict[str, Any] | None,
3004
3064
  ) -> Any:
3005
3065
  """Turn an async view write's immediate response into a final one.
3006
3066
 
@@ -3017,12 +3077,7 @@ def _settle_async_view_write(
3017
3077
  if dataset_id is None or not isinstance(data, dict):
3018
3078
  return data
3019
3079
  dataset_id = int(dataset_id)
3020
- try:
3021
- row_check: dict[str, Any] = {
3022
- "rows_before": _row_count_now(service, dataset_id, view_id, project_id)
3023
- }
3024
- except CliError as exc:
3025
- row_check = {"rows_before": None, "rows_before_error": f"{type(exc).__name__}: {exc}"}
3080
+ row_check: dict[str, Any] = dict(before or {"rows_before": None})
3026
3081
  rows_after, pipeline_error = wait_for_view_row_count(service, dataset_id, view_id, project_id)
3027
3082
  row_check["rows_after"] = rows_after
3028
3083
  data = {**data, "row_check": row_check}
@@ -674,6 +674,23 @@ def _require_clone_from_same_dataset(service: Any, clone_from: int, dataset_id:
674
674
  )
675
675
 
676
676
 
677
+ _DASHBOARDS_LIST_SYMBOL = "mammoth.api.dashboards.DashboardsAPI.list"
678
+
679
+
680
+ def _with_dashboards(service: Any, view_id: int, record: Any) -> Any:
681
+ """Add the dashboards built on this view, so a user on the view's page who
682
+ says "this board" is understood (eval T1-D-22); unchanged when none."""
683
+ if not isinstance(record, dict):
684
+ return record
685
+ boards = service.call(_DASHBOARDS_LIST_SYMBOL)
686
+ built_on = [
687
+ {"id": board.get("id"), "title": board.get("title")}
688
+ for board in (boards if isinstance(boards, list) else [])
689
+ if isinstance(board, dict) and view_id in (board.get("sources") or [])
690
+ ]
691
+ return {**record, "dashboards": built_on} if built_on else record
692
+
693
+
677
694
  def view_get(invocation: Invocation) -> HandlerResult:
678
695
  """Get one view by id, optionally scoped to an exact dataset parent."""
679
696
  view_id = _view_id(invocation)
@@ -699,7 +716,8 @@ def view_get(invocation: Invocation) -> HandlerResult:
699
716
  with open_service(invocation) as (service, auth):
700
717
  data = service.call("mammoth.api.dataviews.DataviewsAPI.get", **kwargs)
701
718
  parents.remember(_profile_name(invocation), auth.workspace_id, {view_id: dataset_id})
702
- data = apply_column_renames(data) if document.get("fields") else brief_view_record(data)
719
+ data = apply_column_renames(data) if document.get("fields") else brief_view_record(data)
720
+ data = _with_dashboards(service, view_id, data)
703
721
  return data, _meta(invocation, auth.workspace_id)
704
722
  context: dict[str, Any] = {"view_id": view_id}
705
723
  if dataset_id is not None:
@@ -718,12 +736,13 @@ def view_get(invocation: Invocation) -> HandlerResult:
718
736
  data = service.call(_symbol(invocation), **kwargs)
719
737
  payload = _view_payload(data)
720
738
  parents.remember_records(_profile_name(invocation), auth.workspace_id, payload)
721
- if not document.get("fields"):
722
- # The discovery path returns the standard record; trim it to the same
723
- # brief shape the exact path asks the server for.
724
- payload = brief_view_record(payload)
725
- else:
726
- payload = apply_column_renames(payload)
739
+ if not document.get("fields"):
740
+ # The discovery path returns the standard record; trim it to the same
741
+ # brief shape the exact path asks the server for.
742
+ payload = brief_view_record(payload)
743
+ else:
744
+ payload = apply_column_renames(payload)
745
+ payload = _with_dashboards(service, view_id, payload)
727
746
  return payload, _meta(invocation, auth.workspace_id)
728
747
 
729
748
 
@@ -0,0 +1,62 @@
1
+ """Dataset health, stated wherever a dataset appears in a result.
2
+
3
+ A dataset's ``status`` can say it did not load cleanly, but a bare status word
4
+ reads as fine to an agent that is looking for rows. :func:`with_dataset_health`
5
+ turns each such status into a ``dataset_health`` entry that says what it means
6
+ and which command fixes it. It runs on every result, read or write, and never
7
+ makes a request.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Iterator
13
+ from typing import Any
14
+
15
+ #: Status to (what it means, which command to run first) -- ``{id}`` is filled in.
16
+ _UNHEALTHY: dict[str, tuple[str, str]] = {
17
+ "has_unstructured_data": (
18
+ "Some rows of this file could not be split into columns, so they are held "
19
+ "apart and the dataset may have few or no usable rows. The usual cause is "
20
+ "the wrong delimiter or quote character.",
21
+ "mammoth dataset broken-rows list {id} shows the rows and why each does "
22
+ "not fit; then mammoth dataset file-settings get {id} and mammoth dataset "
23
+ "file-settings update {id} with the right delimiter or quote character",
24
+ ),
25
+ }
26
+ #: Nesting to search: a result, a listing inside it, a record in the listing.
27
+ _MAX_DEPTH = 3
28
+
29
+
30
+ def with_dataset_health(data: Any) -> Any:
31
+ """Return ``data`` with a ``dataset_health`` list when any dataset in it is
32
+ unhealthy; anything else is returned unchanged."""
33
+ if not isinstance(data, dict):
34
+ return data
35
+ health = [_entry(record) for record in _unhealthy(data, 0)]
36
+ if not health:
37
+ return data
38
+ return {**data, "dataset_health": health}
39
+
40
+
41
+ def _unhealthy(value: Any, depth: int) -> Iterator[dict[str, Any]]:
42
+ if depth > _MAX_DEPTH:
43
+ return
44
+ if isinstance(value, dict):
45
+ if value.get("status") in _UNHEALTHY and isinstance(value.get("id"), int):
46
+ yield value
47
+ for child in value.values():
48
+ yield from _unhealthy(child, depth + 1)
49
+ elif isinstance(value, list):
50
+ for child in value:
51
+ yield from _unhealthy(child, depth + 1)
52
+
53
+
54
+ def _entry(record: dict[str, Any]) -> dict[str, Any]:
55
+ detail, fix = _UNHEALTHY[record["status"]]
56
+ return {
57
+ "dataset_id": record["id"],
58
+ "name": record.get("name"),
59
+ "status": record["status"],
60
+ "detail": detail,
61
+ "fix": fix.format(id=record["id"]),
62
+ }
@@ -0,0 +1,86 @@
1
+ """How new data reaches a dataset, stated wherever such a dataset appears.
2
+
3
+ A dataset made from an uploaded file reads as a one-time snapshot, so an agent
4
+ asked for a recurring report concludes nothing new will ever arrive; the next
5
+ file appends to it as a new batch and its views re-run. A dataset written by a
6
+ view's export reads as a standalone copy, so an agent cannot tell what writes it
7
+ or what deleting it takes with it. :func:`with_new_data_path`
8
+ states either path; it never makes a request.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Iterator
14
+ from typing import Any
15
+
16
+ _FROM_FILE = (
17
+ "Made from an uploaded file; the next period's file joins this dataset as a "
18
+ "new batch with mammoth file upload FILE --input '{{\"append_to_ds_id\": {id}}}'. "
19
+ "Its views re-run on the added rows, so a scheduled automation on a view "
20
+ "sends the updated data without anyone rebuilding the pipeline."
21
+ )
22
+ _FROM_EXPORT = (
23
+ "Written by export {export_id} in view {view_id}'s pipeline, which fills it "
24
+ "again on every run of that view. Deleting this dataset also removes that "
25
+ "export; to keep the dataset but stop the updates, run mammoth view export "
26
+ "delete {view_id} {export_id}."
27
+ )
28
+ #: Nesting to search: a result, a listing inside it, a record in the listing.
29
+ _MAX_DEPTH = 3
30
+
31
+
32
+ def with_new_data_path(data: Any) -> Any:
33
+ """Return ``data`` with a ``new_data`` list when any dataset in it was made
34
+ from an uploaded file or is written by a view's export; anything else is
35
+ returned unchanged."""
36
+ if not isinstance(data, dict):
37
+ return data
38
+ paths = [
39
+ {"dataset_id": record["id"], "name": record.get("name"), "how": how}
40
+ for record, how in _fed_datasets(data, 0)
41
+ ]
42
+ if not paths:
43
+ return data
44
+ return {**data, "new_data": paths}
45
+
46
+
47
+ def with_file_upload_path(data: Any, dataset_id: int) -> Any:
48
+ """Return file settings with a ``new_data`` entry: settings with a delimiter
49
+ exist only for a parsed upload, which the next period's file can join."""
50
+ info = data.get("info") if isinstance(data, dict) else None
51
+ if not isinstance(info, dict) or not info.get("delimiter"):
52
+ return data
53
+ how = _FROM_FILE.format(id=dataset_id)
54
+ return {**data, "new_data": [{"dataset_id": dataset_id, "name": None, "how": how}]}
55
+
56
+
57
+ def _fed_datasets(value: Any, depth: int) -> Iterator[tuple[dict[str, Any], str]]:
58
+ if depth > _MAX_DEPTH:
59
+ return
60
+ if isinstance(value, dict):
61
+ how = _how(value)
62
+ if how is not None:
63
+ yield value, how
64
+ for child in value.values():
65
+ yield from _fed_datasets(child, depth + 1)
66
+ elif isinstance(value, list):
67
+ for child in value:
68
+ yield from _fed_datasets(child, depth + 1)
69
+
70
+
71
+ def _how(record: dict[str, Any]) -> str | None:
72
+ if not isinstance(record.get("id"), int):
73
+ return None
74
+ info = record.get("additional_info")
75
+ if (
76
+ isinstance(info, dict)
77
+ and isinstance(info.get("DATAVIEW_ID"), int)
78
+ and isinstance(info.get("TRIGGER_ID"), int)
79
+ ):
80
+ return _FROM_EXPORT.format(view_id=info["DATAVIEW_ID"], export_id=info["TRIGGER_ID"])
81
+ sources = record.get("sources")
82
+ if isinstance(sources, list) and any(
83
+ isinstance(s, dict) and s.get("type") == "file" for s in sources
84
+ ):
85
+ return _FROM_FILE.format(id=record["id"])
86
+ return None
@@ -22,6 +22,11 @@ JOIN_MATCH_RATE_THRESHOLD = 0.8
22
22
  #: view write can carry that runs a pipeline -- the only kind that can leave a
23
23
  #: downstream export referencing something the write just changed or removed.
24
24
  _VIEW_PIPELINE_MUTATION_CLASS = "reversible_pipeline"
25
+ #: Pipeline writes outside that class that can still drop or rename a column
26
+ #: a saved export reads: deleting a step, renaming, editing the pipeline.
27
+ _EXPORT_BREAKING_COMMANDS = frozenset(
28
+ {"view.task.delete", "view.transform.rename-columns", "view.pipeline.edit"}
29
+ )
25
30
 
26
31
  _VIEW_EXPORT_LIST_COMMAND = "view.export.list"
27
32
 
@@ -336,11 +341,14 @@ def _apply_downstream_export_check(verify: dict[str, Any], invocation: Invocatio
336
341
 
337
342
 
338
343
  def _pipeline_view_id(invocation: Invocation) -> int | None:
339
- """The view id a ``reversible_pipeline`` write acted on, or None otherwise."""
344
+ """The view id a write that can break an export acted on, or None otherwise."""
340
345
  from mammoth_cli.manifest.loader import command_by_id
341
346
 
342
347
  record = command_by_id(invocation.command_id) or {}
343
- if record.get("mutation_class") != _VIEW_PIPELINE_MUTATION_CLASS:
348
+ if (
349
+ record.get("mutation_class") != _VIEW_PIPELINE_MUTATION_CLASS
350
+ and invocation.command_id not in _EXPORT_BREAKING_COMMANDS
351
+ ):
344
352
  return None
345
353
  for name in ("view_id", "dataview_id"):
346
354
  value = invocation.positional(name)
@@ -101,6 +101,11 @@ def compile_condition(spec: Any) -> _ConditionResult:
101
101
  if not isinstance(spec, dict):
102
102
  raise _invalid("A condition must be a mapping.")
103
103
 
104
+ compound = [key for key in ("and", "or", "not") if key in spec]
105
+ if compound and len(spec) != 1:
106
+ raise _invalid(
107
+ f"'{compound[0]}' must be the condition's only key; nest the others inside it."
108
+ )
104
109
  if "and" in spec or "or" in spec:
105
110
  key = "and" if "and" in spec else "or"
106
111
  branches = spec[key]
@@ -23,6 +23,8 @@ _MIN_VALUES = 3
23
23
  _MAX_EXAMPLES = 3
24
24
  #: Most groups of variant spellings to name in one warning.
25
25
  _MAX_VARIANT_GROUPS = 5
26
+ #: Above this many distinct labels a column is not a set of names to compare pairwise.
27
+ _MAX_LABELS_COMPARED = 200
26
28
 
27
29
  _NUMBER = re.compile(r"^[-+]?[$€£]?\s*\d[\d,]*(\.\d+)?\s*%?$|^[-+]?[$€£]?\s*\.\d+\s*%?$")
28
30
  #: Punctuation is normalised to a space, not dropped, so "Pepsi,Cola" and
@@ -144,6 +146,74 @@ def _variant_spellings_warning(
144
146
  }
145
147
 
146
148
 
149
+ def _parse_date(value: Any) -> datetime | None:
150
+ text = str(value).strip()[:19]
151
+ for fmt in _DATE_FORMATS:
152
+ try:
153
+ return datetime.strptime(text, fmt)
154
+ except ValueError:
155
+ continue
156
+ return None
157
+
158
+
159
+ def _date_spans(
160
+ rows: list[Mapping[str, Any]], column: str, date_column: str
161
+ ) -> dict[str, tuple[datetime, datetime]]:
162
+ """Each label's first and last date, over rows where both parse."""
163
+ spans: dict[str, tuple[datetime, datetime]] = {}
164
+ for row in rows:
165
+ label, day = row.get(column), _parse_date(row.get(date_column))
166
+ if _blank(label) or day is None:
167
+ continue
168
+ first, last = spans.get(str(label), (day, day))
169
+ spans[str(label)] = (min(first, day), max(last, day))
170
+ return spans
171
+
172
+
173
+ def _renamed_label_warning(
174
+ rows: list[Mapping[str, Any]],
175
+ column: str,
176
+ date_column: str,
177
+ view_id: int | None,
178
+ ) -> dict[str, Any] | None:
179
+ """A label that stops before a longer label starting with its words begins
180
+ reads as one thing renamed ("Riverside" -> "Riverside Mall"): left apart, its
181
+ figures split and a period comparison shows a false drop and a false newcomer.
182
+ """
183
+ spans = _date_spans(rows, column, date_column)
184
+ if len(spans) > _MAX_LABELS_COMPARED:
185
+ return None
186
+ words = {label: _spelling_key(label).split() for label in spans}
187
+ pairs = [
188
+ (old, new)
189
+ for old in spans
190
+ for new in spans
191
+ if 0 < len(words[old]) < len(words[new])
192
+ and words[new][: len(words[old])] == words[old]
193
+ and spans[old][1] < spans[new][0]
194
+ ]
195
+ if not pairs:
196
+ return None
197
+ shown = pairs[:_MAX_VARIANT_GROUPS]
198
+ examples = "; ".join(
199
+ f"{old!r} last on {spans[old][1]:%Y-%m-%d}, {new!r} first on {spans[new][0]:%Y-%m-%d}"
200
+ for old, new in shown
201
+ )
202
+ mapping = [{"search": [old], "replace": new} for old, new in shown]
203
+ return {
204
+ "column": column,
205
+ "issue": "renamed_label",
206
+ "detail": (
207
+ f"These labels never share a date and the later one starts with the earlier "
208
+ f"one's words, so each reads as one {column} renamed: {examples}. Left apart, "
209
+ "its figures split across two names and a period comparison shows a drop "
210
+ "and a new entry that are the same thing; confirm, then unify them."
211
+ ),
212
+ "fix": _bulk_replace_hint(view_id, column, mapping),
213
+ "rows_checked": len(rows),
214
+ }
215
+
216
+
147
217
  def _duplicate_rows_warning(
148
218
  rows: list[Mapping[str, Any]],
149
219
  view_id: int | None,
@@ -246,7 +316,7 @@ def column_warnings(
246
316
  Returns:
247
317
  One record per finding: ``column``, ``issue``
248
318
  (``numbers_stored_as_text``, ``dates_stored_as_text``,
249
- ``variant_spellings``, ``blank_values`` or the table-level
319
+ ``variant_spellings``, ``renamed_label``, ``blank_values`` or the table-level
250
320
  ``duplicate_rows``), ``detail`` and, where one command fixes it,
251
321
  ``fix``. Counts are over the rows given (``rows_checked`` on each
252
322
  record).
@@ -306,6 +376,17 @@ def column_warnings(
306
376
  variant_warning = _variant_spellings_warning(column, texts, view_id, checked)
307
377
  if variant_warning is not None:
308
378
  warnings.append(variant_warning)
379
+ date_column = next(
380
+ (name for name, kind in column_types.items() if str(kind).upper() == "DATE"),
381
+ None,
382
+ )
383
+ renamed = (
384
+ _renamed_label_warning(materialised, column, date_column, view_id)
385
+ if date_column is not None
386
+ else None
387
+ )
388
+ if renamed is not None:
389
+ warnings.append(renamed)
309
390
  if blanks:
310
391
  remove_fix = None
311
392
  if figures == [column] and view_id is not None and dataset_id is not None: