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.
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/PKG-INFO +1 -1
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +1 -1
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +11 -1
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/view.py +232 -50
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/view_ops.py +53 -1
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/errors/envelope.py +4 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/command_contract.py +9 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/pyproject.toml +1 -1
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/view.yaml +18 -13
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/.gitignore +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/LICENSE +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/README.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/app.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/auth.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/calc.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/job.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/link.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/project.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/schema.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/view_profile.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/embed.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/confirm.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dataset_health.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dryrun.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dryrun_targets.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/embedded.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/intent_only.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/invocation.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/new_data.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/session.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/state.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/runtime/verify.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/board_values.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/conditional_format.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_filters.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_pages.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_review.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/data_profile.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/data_quality.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/input_fields.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/listing.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/local_time.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/mapping.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/positionals.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/read_queries.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/sdk_service.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/sql_check.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/stored_stats.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/testing.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/text_dates.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/services/write_impact.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/addon.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/agent.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/ai.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/annotation.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/auth.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/automation.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/batch.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/billing.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/calc.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/client-app.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/completion.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/config.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/connector.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/context.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/dashboard.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/data-app.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/dataset.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/external-key.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/file.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/folder.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/link.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/notification.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/parameter.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/project.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/schedule.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/skill.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/snippet.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/support.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/template.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/trash.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/upgrade.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/user.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/webhook.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/workflow.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/commands/workspace.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/edits-target.source.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/schema-v1.json +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/sdk-catalog.source.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/manifests/sdk-methods.yaml +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.2.30 → mammoth_cli-2.2.31}/spec/openapi/openapi.json +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
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
|
|
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
|
-
|
|
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
|