mammoth-cli 2.0.77__tar.gz → 2.0.79__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.0.77 → mammoth_cli-2.0.79}/PKG-INFO +1 -1
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/app.py +11 -4
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +18 -18
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +2 -2
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +1 -1
- mammoth_cli-2.0.79/mammoth_cli/runtime/state.py +413 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/verify.py +51 -3
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/data_quality.py +9 -1
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/pyproject.toml +1 -1
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/addon.yaml +24 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/agent.yaml +12 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/ai.yaml +3 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/annotation.yaml +19 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/auth.yaml +8 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/automation.yaml +24 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/batch.yaml +25 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/billing.yaml +39 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/calc.yaml +2 -1
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/client-app.yaml +14 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/completion.yaml +5 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/config.yaml +5 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/connector.yaml +53 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/context.yaml +8 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/dashboard.yaml +413 -70
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/data-app.yaml +29 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/dataset.yaml +50 -2
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/external-key.yaml +9 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/file.yaml +38 -9
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/folder.yaml +28 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/notification.yaml +16 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/parameter.yaml +44 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/project.yaml +50 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/schedule.yaml +14 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/skill.yaml +13 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/snippet.yaml +24 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/support.yaml +142 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/template.yaml +14 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/trash.yaml +8 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/upgrade.yaml +4 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/user.yaml +21 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/view.yaml +565 -115
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/webhook.yaml +16 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/workflow.yaml +48 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/workspace.yaml +54 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/schema-v1.json +15 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/.gitignore +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/LICENSE +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/README.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/auth.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/calc.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/job.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/project.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/schema.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/view.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/view_ops.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/embed.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/errors/envelope.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/confirm.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/dryrun.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/embedded.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/invocation.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/session.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/command_contract.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/dashboard_pages.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/dashboard_review.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/input_fields.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/mapping.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/positionals.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/sdk_service.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/testing.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/sdk-catalog.source.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/sdk-methods.yaml +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/openapi/openapi.json +0 -0
|
@@ -43,6 +43,7 @@ from mammoth_cli.output.policy import (
|
|
|
43
43
|
)
|
|
44
44
|
from mammoth_cli.runtime import executor, validate
|
|
45
45
|
from mammoth_cli.runtime.invocation import Invocation
|
|
46
|
+
from mammoth_cli.runtime.state import with_state
|
|
46
47
|
from mammoth_cli.runtime.strict import validate_extra_args
|
|
47
48
|
from mammoth_cli.runtime.verify import with_verify
|
|
48
49
|
from mammoth_cli.services.positionals import PositionalSpec, resolve_positionals
|
|
@@ -825,7 +826,7 @@ def _execute(invocation: Invocation) -> None:
|
|
|
825
826
|
if invocation.dry_run:
|
|
826
827
|
return _dry_run(handler, invocation)
|
|
827
828
|
data, meta = handler(invocation)
|
|
828
|
-
return _apply_verify(invocation
|
|
829
|
+
return _apply_verify(invocation, data), meta
|
|
829
830
|
|
|
830
831
|
executor.run(
|
|
831
832
|
invocation.command_id,
|
|
@@ -837,17 +838,23 @@ def _execute(invocation: Invocation) -> None:
|
|
|
837
838
|
)
|
|
838
839
|
|
|
839
840
|
|
|
840
|
-
def _apply_verify(
|
|
841
|
+
def _apply_verify(invocation: Invocation, data: Any) -> Any:
|
|
841
842
|
"""Add the automatic write read-back to a real, mutating, API-backed result.
|
|
842
843
|
|
|
843
844
|
A read command and a command with no reviewed ``sdk_symbol`` (a local or
|
|
844
845
|
not-yet-backed command) are returned unchanged; ``--dry-run`` never
|
|
845
846
|
reaches this at all, since it returns before the handler's real call.
|
|
847
|
+
|
|
848
|
+
Two independent read-backs are layered on top of the raw result: ``verify``
|
|
849
|
+
(status/row-count signals derived from the write's own response) and
|
|
850
|
+
``state`` (a fresh read of what the write actually produced, per the
|
|
851
|
+
command's manifest ``readback``/``no_readback`` declaration -- see
|
|
852
|
+
:mod:`mammoth_cli.runtime.state`).
|
|
846
853
|
"""
|
|
847
|
-
record = command_by_id(command_id) or {}
|
|
854
|
+
record = command_by_id(invocation.command_id) or {}
|
|
848
855
|
if record.get("mutation_class") == "read" or not record.get("sdk_symbol"):
|
|
849
856
|
return data
|
|
850
|
-
return with_verify(data)
|
|
857
|
+
return with_state(invocation, with_verify(data, invocation))
|
|
851
858
|
|
|
852
859
|
|
|
853
860
|
def _dry_run(handler: Handler, invocation: Invocation) -> tuple[Any, dict[str, Any]]:
|
|
@@ -42,12 +42,16 @@ never read one from a file/env var, never run `auth login`
|
|
|
42
42
|
yourself. If any envelope carries `meta.update_available`, run its `command`
|
|
43
43
|
before the next step.
|
|
44
44
|
|
|
45
|
+
In a shell: piped stdout is compact JSON and prompts are off; in a session
|
|
46
|
+
that is not piped, `export MAMMOTH_OUTPUT=json MAMMOTH_NO_INPUT=1` once.
|
|
47
|
+
`project ensure` saves the active project (`--project`, `MAMMOTH_PROFILE`
|
|
48
|
+
and `MAMMOTH_PROJECT` override it). `mammoth skill show --input '{"file":
|
|
49
|
+
"references/recipes/transforms.md"}'` prints any file of this skill;
|
|
50
|
+
`mammoth skill agents-md install` writes a steering block into a
|
|
51
|
+
repository's AGENTS.md so later sessions start here.
|
|
52
|
+
|
|
45
53
|
## Defaults you do not repeat
|
|
46
54
|
|
|
47
|
-
- Piped stdout is compact JSON and prompts are off. Only in a session that
|
|
48
|
-
is not piped, `export MAMMOTH_OUTPUT=json MAMMOTH_NO_INPUT=1` once.
|
|
49
|
-
- `project ensure` saves the active project; `--project` only overrides it.
|
|
50
|
-
`MAMMOTH_PROFILE` / `MAMMOTH_PROJECT` also work as session defaults.
|
|
51
55
|
- The CLI remembers which dataset owns each view from any read (`view list
|
|
52
56
|
DATASET_ID`, `view get VIEW_ID`, an upload). After that, no `dataset_id`
|
|
53
57
|
on transforms, exports or deletes. If a command asks for the parent
|
|
@@ -96,10 +100,10 @@ with the command tried.
|
|
|
96
100
|
Users often upload related files without saying how they relate ("make a
|
|
97
101
|
dashboard from t_a and t_b"). Work it out from the data before building:
|
|
98
102
|
|
|
99
|
-
1. Upload every file
|
|
100
|
-
made of it (`view_id`, column
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
1. Upload every file, or find the ones already uploaded. An upload result
|
|
104
|
+
carries a `view` preview of what Mammoth made of it (`view_id`, column
|
|
105
|
+
types, sample rows, `column_warnings`, `before_dashboard`). What Mammoth
|
|
106
|
+
made of the files is what counts, not the raw files. Run `view data get VIEW_ID` per view;
|
|
103
107
|
warnings list numbers/dates stored as text, and blanks, each with its fix.
|
|
104
108
|
2. Find the keys. A column in one view whose values appear in a column of
|
|
105
109
|
the other (`customer_id` and `id`, `order_ref` and `order_no`) is a
|
|
@@ -114,8 +118,8 @@ dashboard from t_a and t_b"). Work it out from the data before building:
|
|
|
114
118
|
is a miss.
|
|
115
119
|
5. `view transform join` the lookup view into the main view (`LEFT`). The
|
|
116
120
|
result's `join_check` gives `match_rate`, `unmatched_rows` and
|
|
117
|
-
`unmatched_keys`; put them in your report
|
|
118
|
-
|
|
121
|
+
`unmatched_keys`; put them in your report. When the result's
|
|
122
|
+
`verify.needs_user` is set, stop and tell the user before building on it.
|
|
119
123
|
6. If the data has money, add it before you make the dashboard: `revenue`
|
|
120
124
|
(`math`, `qty * price`, `new_column`), the `fix` in `before_dashboard`.
|
|
121
125
|
A dashboard sees only the columns the view had when it was made.
|
|
@@ -134,9 +138,6 @@ your report. If no column links the files, ask before you combine them.
|
|
|
134
138
|
|
|
135
139
|
- Ids are positionals; request fields are one `--input '{...}'` document;
|
|
136
140
|
no per-field flags (`project create NAME`, not `--name`).
|
|
137
|
-
- `mammoth skill show --input '{"file": "references/recipes/transforms.md"}'`
|
|
138
|
-
prints any file of this skill; `mammoth skill agents-md install` writes a
|
|
139
|
-
steering block into a repository's AGENTS.md so later sessions start here.
|
|
140
141
|
- `COMMAND --help` lists input fields; `schema find WORDS` inlines
|
|
141
142
|
`accepted_fields`/`agent_example` for top matches, else `mammoth schema
|
|
142
143
|
get COMMAND_ID` (`--input '{"full": true}'` for the schema); `schema
|
|
@@ -161,11 +162,10 @@ your report. If no column links the files, ask before you combine them.
|
|
|
161
162
|
|
|
162
163
|
## Verify before you report
|
|
163
164
|
|
|
164
|
-
- A success envelope proves the call
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
before the change.
|
|
165
|
+
- A success envelope proves the call; the write's `state` (the data,
|
|
166
|
+
object or job status read back after it settled) shows the outcome.
|
|
167
|
+
Check it against your plan: rows the change should and should not have
|
|
168
|
+
touched. `unreadable` or a partial sample: read it with `view data get`.
|
|
169
169
|
- A uniform result (every amount 0, every region "Unknown", most join rows
|
|
170
170
|
unmatched) means the previous step went wrong; stop and re-inspect it.
|
|
171
171
|
- `column_warnings` on a data read and `join_check` on a join are findings,
|
|
@@ -96,9 +96,9 @@ Status on release: ran once on CLI 2.0.40 through `view.task.add` and was read b
|
|
|
96
96
|
|
|
97
97
|
Run: `mammoth view transform delete-columns`. Exact input fields: `mammoth schema get view.transform.delete-columns`.
|
|
98
98
|
|
|
99
|
-
Example: `mammoth view transform delete-columns 123 --input '{"columns": ["Status"], "dataset_id": 456}'`.
|
|
99
|
+
Example: `mammoth view transform delete-columns 123 --input '{"columns": ["Status"], "dataset_id": 456}'`. Illustrative only: append `--yes` after observing an owned target.
|
|
100
100
|
|
|
101
|
-
Result: `ViewTransformDeleteColumnsResult`; mutation `reversible_pipeline`, confirmation `
|
|
101
|
+
Result: `ViewTransformDeleteColumnsResult`; mutation `reversible_pipeline`, confirmation `prompt_or_yes`, wait policy `always_wait`.
|
|
102
102
|
|
|
103
103
|
Status on release: ran once on CLI 2.0.40 through `view.task.add` and was read back with `view data get`; other inputs for this transform are untried.
|
|
104
104
|
|
|
@@ -7,7 +7,7 @@ sentence with `v3 generate`, and change it with `chat edit`; author a canvas by
|
|
|
7
7
|
hand (`create-blank`, `canvas save`, `pages add`) only when asked to.
|
|
8
8
|
`dashboard source list` is an observed blocker on release (see
|
|
9
9
|
capabilities); verify the view binding with `dashboard get DASHBOARD_ID`
|
|
10
|
-
(`data.
|
|
10
|
+
(`data.sources`) instead.
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
mammoth dashboard v3 generate --input '{"body": {"params": {"intent": "Monthly revenue by region, with top customers", "dataview_id": VIEW_ID}}}'
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""Automatic write read-back: attach the write's new state to its result.
|
|
2
|
+
|
|
3
|
+
``with_verify`` (see :mod:`mammoth_cli.runtime.verify`) tells the caller
|
|
4
|
+
whether a write succeeded. It does not show what the write actually produced:
|
|
5
|
+
an export can report ``verified: true`` while the dataset it wrote has one of
|
|
6
|
+
four expected columns, and nothing in the result would say so. :func:`with_state`
|
|
7
|
+
closes that gap: every manifest command whose ``mutation_class`` is not
|
|
8
|
+
``read`` declares, in its command record, either a ``readback`` (a read
|
|
9
|
+
command plus how to source its arguments from this write's input/result, plus
|
|
10
|
+
a ``kind``) or a ``no_readback`` reason. After the write settles, the executor
|
|
11
|
+
runs the declared read and attaches a compact ``state`` block built from it --
|
|
12
|
+
never from the request, and never omitted: a read that fails still yields a
|
|
13
|
+
``state`` (``kind: unreadable``) rather than silence.
|
|
14
|
+
|
|
15
|
+
See ``CONTRACT-write-state.md`` (D-077) for the manifest shape this reads.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from mammoth_cli.errors.envelope import CliError
|
|
24
|
+
from mammoth_cli.manifest.loader import command_by_id
|
|
25
|
+
from mammoth_cli.runtime.invocation import Invocation
|
|
26
|
+
from mammoth_cli.services.positionals import resolve_positionals
|
|
27
|
+
|
|
28
|
+
#: Total JSON size a ``state`` block may reach before trimming kicks in.
|
|
29
|
+
STATE_SIZE_CAP_BYTES = 1500
|
|
30
|
+
|
|
31
|
+
#: Sample rows a ``kind: data`` state keeps, and the char length each cell is
|
|
32
|
+
#: truncated to -- row count and columns are never dropped to make room.
|
|
33
|
+
_SAMPLE_ROW_CAP = 5
|
|
34
|
+
_CELL_CHAR_CAP = 80
|
|
35
|
+
|
|
36
|
+
_VIEW_GET_COMMAND = "view.get"
|
|
37
|
+
_VIEW_DATA_COMMAND = "view.data.get"
|
|
38
|
+
_VIEW_LIST_COMMAND = "view.list"
|
|
39
|
+
|
|
40
|
+
#: ``verify.state`` of a write that was accepted but has not finished.
|
|
41
|
+
_UNFINISHED_VERIFY_STATE = "processing"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def with_state(invocation: Invocation, data: Any) -> Any:
|
|
45
|
+
"""Return ``data`` with a ``state`` read-back block added, per the manifest.
|
|
46
|
+
|
|
47
|
+
A command with no ``readback``/``no_readback`` declared yet (mid-rollout)
|
|
48
|
+
is returned unchanged, as is a command that declares ``no_readback`` --
|
|
49
|
+
there is nothing to read back, by the family owner's own account.
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(data, dict):
|
|
52
|
+
return data
|
|
53
|
+
record = command_by_id(invocation.command_id) or {}
|
|
54
|
+
readback = record.get("readback")
|
|
55
|
+
if not isinstance(readback, dict):
|
|
56
|
+
return data
|
|
57
|
+
unfinished = _unfinished_state(data)
|
|
58
|
+
state = unfinished or _enforce_cap(_build_state(invocation, data, readback))
|
|
59
|
+
return {**data, "state": state}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _unfinished_state(data: dict[str, Any]) -> dict[str, Any] | None:
|
|
63
|
+
"""A write ``verify`` found still processing has no new state to read yet."""
|
|
64
|
+
verify = data.get("verify")
|
|
65
|
+
if not isinstance(verify, dict) or verify.get("state") != _UNFINISHED_VERIFY_STATE:
|
|
66
|
+
return None
|
|
67
|
+
return {
|
|
68
|
+
"kind": "delivery",
|
|
69
|
+
"read_by": "verify",
|
|
70
|
+
"status": _UNFINISHED_VERIFY_STATE,
|
|
71
|
+
"detail": verify.get("reason"),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _build_state(
|
|
76
|
+
invocation: Invocation, data: dict[str, Any], readback: dict[str, Any]
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
read_command_id = str(readback["command"])
|
|
79
|
+
kind = str(readback["kind"])
|
|
80
|
+
ids_spec: dict[str, Any] = readback.get("ids") or {}
|
|
81
|
+
resolved_ids: dict[str, Any] = {}
|
|
82
|
+
try:
|
|
83
|
+
resolved_ids = {
|
|
84
|
+
name: _resolve_source(str(source), data, invocation)
|
|
85
|
+
for name, source in ids_spec.items()
|
|
86
|
+
}
|
|
87
|
+
missing = [name for name, value in resolved_ids.items() if value is None]
|
|
88
|
+
if missing:
|
|
89
|
+
raise ValueError(f"could not resolve id(s) {missing} from the write's result/input")
|
|
90
|
+
if kind == "data":
|
|
91
|
+
return _data_state(resolved_ids, invocation)
|
|
92
|
+
read_data = _call_read(read_command_id, resolved_ids, invocation)
|
|
93
|
+
read_by = _read_by_label(read_command_id, resolved_ids)
|
|
94
|
+
if kind == "object":
|
|
95
|
+
return _object_state(read_by, read_data)
|
|
96
|
+
if kind == "delivery":
|
|
97
|
+
return _delivery_state(read_by, read_data)
|
|
98
|
+
raise ValueError(f"unknown readback kind {kind!r}")
|
|
99
|
+
except Exception as exc: # noqa: BLE001 -- a read failure must surface, never crash the write
|
|
100
|
+
read_by = _read_by_label(read_command_id, resolved_ids)
|
|
101
|
+
reason = exc.message if isinstance(exc, CliError) else str(exc)
|
|
102
|
+
return {"kind": "unreadable", "read_by": read_by, "reason": reason or type(exc).__name__}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _read_by_label(command_id: str, resolved_ids: dict[str, Any]) -> str:
|
|
106
|
+
representative = next(iter(resolved_ids.values()), None)
|
|
107
|
+
return f"{command_id} {representative}" if representative is not None else command_id
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# id resolution
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _resolve_source(source: str, data: dict[str, Any], invocation: Invocation) -> Any:
|
|
116
|
+
"""Resolve one ``ids`` value; ``a|b`` takes the first alternative that resolves.
|
|
117
|
+
|
|
118
|
+
A write whose response comes in more than one shape (an export returns
|
|
119
|
+
either ``{trigger_id, future_id}`` or ``{"job": {...}}``) names each place
|
|
120
|
+
its id can be.
|
|
121
|
+
"""
|
|
122
|
+
for alternative in source.split("|"):
|
|
123
|
+
value = _resolve_one_source(alternative, data, invocation)
|
|
124
|
+
if value is not None:
|
|
125
|
+
return value
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _resolve_one_source(source: str, data: dict[str, Any], invocation: Invocation) -> Any:
|
|
130
|
+
"""Resolve one ``result.<path>`` | ``input.<field>`` | ``positional.<n>`` source."""
|
|
131
|
+
if source.startswith("result."):
|
|
132
|
+
return _get_path(data, source[len("result.") :])
|
|
133
|
+
if source.startswith("input."):
|
|
134
|
+
document = invocation.bound_input() or {}
|
|
135
|
+
return _get_path(document, source[len("input.") :])
|
|
136
|
+
if source.startswith("positional."):
|
|
137
|
+
index_text = source[len("positional.") :]
|
|
138
|
+
try:
|
|
139
|
+
index = int(index_text)
|
|
140
|
+
except ValueError:
|
|
141
|
+
raise ValueError(f"invalid positional index in id source {source!r}") from None
|
|
142
|
+
return invocation.extra_args[index] if index < len(invocation.extra_args) else None
|
|
143
|
+
raise ValueError(f"unrecognized id source {source!r}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _get_path(root: Any, path: str) -> Any:
|
|
147
|
+
current = root
|
|
148
|
+
for segment in path.split("."):
|
|
149
|
+
if isinstance(current, dict):
|
|
150
|
+
current = current.get(segment)
|
|
151
|
+
elif isinstance(current, list) and segment.isdigit():
|
|
152
|
+
index = int(segment)
|
|
153
|
+
current = current[index] if index < len(current) else None
|
|
154
|
+
else:
|
|
155
|
+
return None
|
|
156
|
+
return current
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# dispatching a read command's handler directly (never through the executor:
|
|
161
|
+
# this is an internal follow-up read, not a fresh command invocation)
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _read_invocation(command_id: str, resolved_ids: dict[str, Any], base: Invocation) -> Invocation:
|
|
166
|
+
"""Build the :class:`Invocation` a read command's handler expects.
|
|
167
|
+
|
|
168
|
+
Ids that name a declared positional of ``command_id`` are supplied in
|
|
169
|
+
order (matching how handlers read ``extra_args``); any id left over is
|
|
170
|
+
offered as a pre-admitted ``--input`` document instead.
|
|
171
|
+
"""
|
|
172
|
+
positional_names = [spec.name for spec in resolve_positionals(command_id)]
|
|
173
|
+
positionals: dict[str, Any] = {}
|
|
174
|
+
extra_args: list[str] = []
|
|
175
|
+
for name in positional_names:
|
|
176
|
+
if name not in resolved_ids:
|
|
177
|
+
break
|
|
178
|
+
value = resolved_ids[name]
|
|
179
|
+
positionals[name] = value
|
|
180
|
+
extra_args.append(str(value))
|
|
181
|
+
document = {name: value for name, value in resolved_ids.items() if name not in positionals}
|
|
182
|
+
invocation = Invocation(
|
|
183
|
+
command_id=command_id,
|
|
184
|
+
output="json",
|
|
185
|
+
profile=base.profile,
|
|
186
|
+
project=base.project,
|
|
187
|
+
timeout=base.timeout,
|
|
188
|
+
job_timeout=base.job_timeout,
|
|
189
|
+
pipeline_timeout=base.pipeline_timeout,
|
|
190
|
+
no_input=True,
|
|
191
|
+
positionals=positionals,
|
|
192
|
+
extra_args=extra_args,
|
|
193
|
+
)
|
|
194
|
+
object.__setattr__(invocation, "_prepared_input", document or None)
|
|
195
|
+
return invocation
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _call_read(command_id: str, resolved_ids: dict[str, Any], base: Invocation) -> Any:
|
|
199
|
+
from mammoth_cli.commands.registry import HANDLERS
|
|
200
|
+
|
|
201
|
+
handler = HANDLERS.get(command_id)
|
|
202
|
+
if handler is None:
|
|
203
|
+
raise LookupError(f"no handler registered for read command {command_id!r}")
|
|
204
|
+
read_invocation = _read_invocation(command_id, resolved_ids, base)
|
|
205
|
+
read_data, _meta = handler(read_invocation)
|
|
206
|
+
return read_data
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# kind: object
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _object_state(read_by: str, read_data: Any) -> dict[str, Any]:
|
|
215
|
+
return {"kind": "object", "read_by": read_by, "object": _extract_object(read_data)}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _extract_object(read_data: Any) -> Any:
|
|
219
|
+
if not isinstance(read_data, dict):
|
|
220
|
+
return read_data
|
|
221
|
+
# A single-record read (``view.get``, ``dataset.get``, ...) carries its own
|
|
222
|
+
# ``id`` at the top level; a listing read (``view.list``, ...) wraps its
|
|
223
|
+
# items under a plural key instead. Checking for ``id`` first keeps a
|
|
224
|
+
# record's own nested list fields (e.g. a dataview's ``metadata`` columns)
|
|
225
|
+
# from being mistaken for the collection.
|
|
226
|
+
if "id" not in read_data:
|
|
227
|
+
listing = _first_list(read_data)
|
|
228
|
+
if listing is not None:
|
|
229
|
+
return [_scalar_fields(item) for item in listing]
|
|
230
|
+
return _scalar_fields(read_data)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _first_list(record: dict[str, Any]) -> list[Any] | None:
|
|
234
|
+
"""The first list-of-dicts value in a ``*.list`` read's result, if any."""
|
|
235
|
+
for value in record.values():
|
|
236
|
+
if isinstance(value, list) and (not value or isinstance(value[0], dict)):
|
|
237
|
+
return value
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _scalar_fields(item: Any) -> Any:
|
|
242
|
+
"""Keep only an object's top-level scalar fields (every id is one)."""
|
|
243
|
+
if not isinstance(item, dict):
|
|
244
|
+
return item
|
|
245
|
+
return {
|
|
246
|
+
key: value
|
|
247
|
+
for key, value in item.items()
|
|
248
|
+
if value is None or isinstance(value, (str, int, float, bool))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
# kind: delivery
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _delivery_state(read_by: str, read_data: Any) -> dict[str, Any]:
|
|
258
|
+
status: Any = None
|
|
259
|
+
detail: Any = None
|
|
260
|
+
if isinstance(read_data, dict):
|
|
261
|
+
status = read_data.get("status")
|
|
262
|
+
if status is None:
|
|
263
|
+
job = read_data.get("job")
|
|
264
|
+
status = job.get("status") if isinstance(job, dict) else None
|
|
265
|
+
detail = read_data.get("message") or read_data.get("detail") or read_data.get("error")
|
|
266
|
+
return {
|
|
267
|
+
"kind": "delivery",
|
|
268
|
+
"read_by": read_by,
|
|
269
|
+
"status": status if isinstance(status, str) else None,
|
|
270
|
+
"detail": str(detail) if detail is not None else None,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
# kind: data -- the write's output view: real columns, row count, 5 rows.
|
|
276
|
+
#
|
|
277
|
+
# No single read command returns columns+type, row_count and sample rows
|
|
278
|
+
# together, so this always makes two fixed internal reads once it has a
|
|
279
|
+
# view id: ``view.get`` for the typed columns and row count, ``view.data.get``
|
|
280
|
+
# for the sample. When the write's own ids only resolve a dataset id (a
|
|
281
|
+
# dataset/upload write, whose output is that dataset's first view), the
|
|
282
|
+
# view id is discovered with one ``view.list`` read first.
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _data_state(resolved_ids: dict[str, Any], base: Invocation) -> dict[str, Any]:
|
|
287
|
+
view_id = _as_int(resolved_ids.get("view_id") or resolved_ids.get("dataview_id"))
|
|
288
|
+
dataset_id = _as_int(resolved_ids.get("dataset_id"))
|
|
289
|
+
if view_id is None:
|
|
290
|
+
if dataset_id is None:
|
|
291
|
+
raise ValueError("readback kind 'data' needs a resolved view_id or dataset_id")
|
|
292
|
+
view_id = _discover_view_id(dataset_id, base)
|
|
293
|
+
view_ids: dict[str, Any] = {"view_id": view_id}
|
|
294
|
+
if dataset_id is not None:
|
|
295
|
+
view_ids["dataset_id"] = dataset_id
|
|
296
|
+
info = _call_read(_VIEW_GET_COMMAND, view_ids, base)
|
|
297
|
+
page = _call_read(_VIEW_DATA_COMMAND, view_ids, base)
|
|
298
|
+
rows = page.get("data") if isinstance(page, dict) else None
|
|
299
|
+
sample = [_trim_row(row) for row in (rows or [])[:_SAMPLE_ROW_CAP]]
|
|
300
|
+
return {
|
|
301
|
+
"kind": "data",
|
|
302
|
+
"read_by": f"{_VIEW_DATA_COMMAND} {view_id}",
|
|
303
|
+
"columns": _columns(info),
|
|
304
|
+
"row_count": info.get("row_count") if isinstance(info, dict) else None,
|
|
305
|
+
"sample": sample,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _discover_view_id(dataset_id: int, base: Invocation) -> int:
|
|
310
|
+
listing = _call_read(_VIEW_LIST_COMMAND, {"dataset_id": dataset_id}, base)
|
|
311
|
+
views = listing.get("dataviews") if isinstance(listing, dict) else None
|
|
312
|
+
if not isinstance(views, list) or not views or not isinstance(views[0], dict):
|
|
313
|
+
raise ValueError(f"dataset {dataset_id} has no view to read its new state from")
|
|
314
|
+
view_id = views[0].get("id")
|
|
315
|
+
if not isinstance(view_id, int):
|
|
316
|
+
raise ValueError(f"dataset {dataset_id}'s first view has no usable id")
|
|
317
|
+
return view_id
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _columns(info: Any) -> list[dict[str, str]]:
|
|
321
|
+
if not isinstance(info, dict):
|
|
322
|
+
return []
|
|
323
|
+
metadata = info.get("metadata")
|
|
324
|
+
if not isinstance(metadata, list):
|
|
325
|
+
return []
|
|
326
|
+
columns: list[dict[str, str]] = []
|
|
327
|
+
for column in metadata:
|
|
328
|
+
if not isinstance(column, dict):
|
|
329
|
+
continue
|
|
330
|
+
name = column.get("display_name") or column.get("internal_name")
|
|
331
|
+
if name is None:
|
|
332
|
+
continue
|
|
333
|
+
columns.append({"name": str(name), "type": str(column.get("type") or "")})
|
|
334
|
+
return columns
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _trim_row(row: Any) -> Any:
|
|
338
|
+
if not isinstance(row, dict):
|
|
339
|
+
return row
|
|
340
|
+
return {key: _trim_cell(value) for key, value in row.items()}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _trim_cell(value: Any) -> Any:
|
|
344
|
+
if isinstance(value, str) and len(value) > _CELL_CHAR_CAP:
|
|
345
|
+
return value[:_CELL_CHAR_CAP] + "..."
|
|
346
|
+
return value
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _as_int(value: Any) -> int | None:
|
|
350
|
+
if isinstance(value, bool):
|
|
351
|
+
return None
|
|
352
|
+
if isinstance(value, int):
|
|
353
|
+
return value
|
|
354
|
+
if isinstance(value, str) and value.strip().lstrip("-").isdigit():
|
|
355
|
+
return int(value)
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# size cap
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _enforce_cap(state: dict[str, Any]) -> dict[str, Any]:
|
|
365
|
+
"""Trim ``state`` under :data:`STATE_SIZE_CAP_BYTES`; never drop columns/row_count."""
|
|
366
|
+
if _size(state) <= STATE_SIZE_CAP_BYTES:
|
|
367
|
+
return state
|
|
368
|
+
kind = state.get("kind")
|
|
369
|
+
if kind == "data":
|
|
370
|
+
sample = list(state.get("sample") or [])
|
|
371
|
+
while sample and _size({**state, "sample": sample}) > STATE_SIZE_CAP_BYTES:
|
|
372
|
+
sample.pop()
|
|
373
|
+
return {**state, "sample": sample}
|
|
374
|
+
if kind == "object":
|
|
375
|
+
capped = {**state, "object": _capped_object(state)}
|
|
376
|
+
original = state.get("object")
|
|
377
|
+
if isinstance(original, list) and len(capped["object"]) < len(original):
|
|
378
|
+
capped["object_total"] = len(original)
|
|
379
|
+
return capped
|
|
380
|
+
if kind == "delivery":
|
|
381
|
+
detail = state.get("detail")
|
|
382
|
+
if isinstance(detail, str):
|
|
383
|
+
overflow = _size(state) - STATE_SIZE_CAP_BYTES
|
|
384
|
+
keep = max(0, len(detail) - overflow - 3)
|
|
385
|
+
return {**state, "detail": detail[:keep] + "..."}
|
|
386
|
+
return state
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _capped_object(state: dict[str, Any]) -> Any:
|
|
390
|
+
obj = state.get("object")
|
|
391
|
+
if isinstance(obj, list):
|
|
392
|
+
items = list(obj)
|
|
393
|
+
while items and _size({**state, "object": items}) > STATE_SIZE_CAP_BYTES:
|
|
394
|
+
items.pop()
|
|
395
|
+
return items
|
|
396
|
+
if isinstance(obj, dict):
|
|
397
|
+
fields = dict(obj)
|
|
398
|
+
protected = {key for key in fields if key == "id" or key.endswith("_id")}
|
|
399
|
+
droppable = sorted(
|
|
400
|
+
(key for key in fields if key not in protected),
|
|
401
|
+
key=lambda key: len(str(fields[key])),
|
|
402
|
+
reverse=True,
|
|
403
|
+
)
|
|
404
|
+
for key in droppable:
|
|
405
|
+
if _size({**state, "object": fields}) <= STATE_SIZE_CAP_BYTES:
|
|
406
|
+
break
|
|
407
|
+
fields.pop(key, None)
|
|
408
|
+
return fields
|
|
409
|
+
return obj
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _size(value: Any) -> int:
|
|
413
|
+
return len(json.dumps(value, default=str))
|
|
@@ -10,7 +10,10 @@ what it should" instead of re-deriving it per command.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from mammoth_cli.runtime.invocation import Invocation
|
|
14
17
|
|
|
15
18
|
#: Below this share of matched rows, a join has enough misses to flag before
|
|
16
19
|
#: the caller builds anything further on it.
|
|
@@ -19,6 +22,10 @@ JOIN_MATCH_RATE_THRESHOLD = 0.8
|
|
|
19
22
|
_FAILURE_STATUSES = frozenset({"error", "failed", "failure"})
|
|
20
23
|
_FAILURE_PIPELINE_STATES = frozenset({"error", "ref_error"})
|
|
21
24
|
|
|
25
|
+
#: HTTP status codes that mean "accepted, not finished" when a write's own
|
|
26
|
+
#: response carries no other completion signal at all.
|
|
27
|
+
_ACCEPTED_STATUS_CODES = frozenset({202})
|
|
28
|
+
|
|
22
29
|
|
|
23
30
|
#: Surfaced in ``warnings`` (and drives ``verified: false``) when a
|
|
24
31
|
#: ``row_check``/``join_check`` was attempted but ``rows_after`` never came
|
|
@@ -39,14 +46,19 @@ _UNSETTLED_REASON = (
|
|
|
39
46
|
)
|
|
40
47
|
|
|
41
48
|
|
|
42
|
-
def with_verify(data: Any) -> Any:
|
|
49
|
+
def with_verify(data: Any, invocation: Invocation | None = None) -> Any:
|
|
43
50
|
"""Return ``data`` with a ``verify`` read-back block added.
|
|
44
51
|
|
|
45
52
|
``data`` that is not a dict (a dry-run report, a bare list, ...) is
|
|
46
|
-
returned unchanged.
|
|
53
|
+
returned unchanged. When ``invocation`` is given, a write whose own
|
|
54
|
+
response is nothing but an unsettled async job (a bare ``job_id``/
|
|
55
|
+
``future_id``, or an HTTP 202 with no job reference at all) is settled
|
|
56
|
+
first -- see :func:`_settle_unfinished_write` -- so it is never reported
|
|
57
|
+
``verified: true`` before it has actually finished.
|
|
47
58
|
"""
|
|
48
59
|
if not isinstance(data, dict):
|
|
49
60
|
return data
|
|
61
|
+
data = _settle_unfinished_write(data, invocation)
|
|
50
62
|
check = _check_dict(data)
|
|
51
63
|
row_count_attempted = _row_count_attempted(check)
|
|
52
64
|
rows_before, rows_after = _row_counts(check) if row_count_attempted else (None, None)
|
|
@@ -80,6 +92,42 @@ def _job_status(data: dict[str, Any]) -> str | None:
|
|
|
80
92
|
return status if isinstance(status, str) else None
|
|
81
93
|
|
|
82
94
|
|
|
95
|
+
def _settle_unfinished_write(data: dict[str, Any], invocation: Invocation | None) -> dict[str, Any]:
|
|
96
|
+
"""Settle a write whose immediate response is an unfinished async job.
|
|
97
|
+
|
|
98
|
+
``wait_if_job`` (every ``MammothService``) already recognizes a bare
|
|
99
|
+
``{"job_id": N}``/``{"future_id": N}`` job reference and polls it to
|
|
100
|
+
completion -- raising on failure or timeout, exactly like every other
|
|
101
|
+
settle in this codebase -- but only a write handler that explicitly calls
|
|
102
|
+
it gets that settle. A write whose result carries no other completion
|
|
103
|
+
signal (no ``status``/``pipeline_state``/nested ``job.status``) must not
|
|
104
|
+
be reported ``verified: true`` on the strength of an unpolled job
|
|
105
|
+
reference, or of an HTTP 202 with no reference to poll at all.
|
|
106
|
+
"""
|
|
107
|
+
if invocation is None or _job_status(data) is not None:
|
|
108
|
+
return data
|
|
109
|
+
if isinstance(data.get("status"), str) or isinstance(data.get("pipeline_state"), str):
|
|
110
|
+
return data
|
|
111
|
+
if _has_bare_job_reference(data):
|
|
112
|
+
from mammoth_cli.runtime.session import open_service
|
|
113
|
+
|
|
114
|
+
with open_service(invocation) as (service, _auth):
|
|
115
|
+
settled = service.wait_if_job(data)
|
|
116
|
+
return settled if isinstance(settled, dict) else data
|
|
117
|
+
if data.get("status_code") in _ACCEPTED_STATUS_CODES:
|
|
118
|
+
# Accepted, with nothing at all to poll: still not finished.
|
|
119
|
+
return {**data, "status": "processing"}
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _has_bare_job_reference(data: dict[str, Any]) -> bool:
|
|
124
|
+
for key in ("job_id", "future_id"):
|
|
125
|
+
value = data.get(key)
|
|
126
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
127
|
+
return True
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
|
|
83
131
|
def _check_dict(data: dict[str, Any]) -> dict[str, Any] | None:
|
|
84
132
|
"""The ``row_check``/``join_check`` dict a command attached, if any."""
|
|
85
133
|
for key in ("row_check", "join_check"):
|