mammoth-cli 2.0.76__tar.gz → 2.0.78__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.76 → mammoth_cli-2.0.78}/PKG-INFO +2 -2
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/app.py +11 -4
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +2 -2
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +1 -1
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/view_ops.py +4 -1
- mammoth_cli-2.0.78/mammoth_cli/runtime/state.py +413 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/verify.py +51 -3
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/data_quality.py +9 -1
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/pyproject.toml +2 -2
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/addon.yaml +24 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/agent.yaml +12 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/ai.yaml +3 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/annotation.yaml +19 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/auth.yaml +8 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/automation.yaml +24 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/batch.yaml +25 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/billing.yaml +39 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/calc.yaml +2 -1
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/client-app.yaml +14 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/completion.yaml +5 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/config.yaml +5 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/connector.yaml +53 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/context.yaml +8 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/dashboard.yaml +413 -70
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/data-app.yaml +29 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/dataset.yaml +50 -2
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/external-key.yaml +9 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/file.yaml +38 -9
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/folder.yaml +28 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/notification.yaml +16 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/parameter.yaml +44 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/project.yaml +50 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/schedule.yaml +14 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/skill.yaml +13 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/snippet.yaml +24 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/support.yaml +142 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/template.yaml +14 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/trash.yaml +8 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/upgrade.yaml +4 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/user.yaml +21 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/view.yaml +566 -116
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/webhook.yaml +16 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/workflow.yaml +48 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/workspace.yaml +54 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/schema-v1.json +15 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/.gitignore +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/LICENSE +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/README.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/auth.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/calc.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/job.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/project.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/schema.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/view.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/embed.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/errors/envelope.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/confirm.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/dryrun.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/embedded.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/invocation.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/session.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/command_contract.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/dashboard_pages.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/dashboard_review.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/input_fields.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/mapping.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/positionals.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/sdk_service.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/testing.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/sdk-catalog.source.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/manifests/sdk-methods.yaml +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.0.76 → mammoth_cli-2.0.78}/spec/openapi/openapi.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mammoth-cli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.78
|
|
4
4
|
Summary: Command-line interface for the Mammoth Analytics platform
|
|
5
5
|
Project-URL: homepage, https://mammoth.io
|
|
6
6
|
Project-URL: repository, https://github.com/EdgeMetric/mammothsdk
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
17
17
|
Requires-Python: <3.15,>=3.12
|
|
18
18
|
Requires-Dist: jsonschema>=4.20
|
|
19
19
|
Requires-Dist: keyring<26,>=25.7
|
|
20
|
-
Requires-Dist: mammoth-io<0.8,>=0.7.
|
|
20
|
+
Requires-Dist: mammoth-io<0.8,>=0.7.36
|
|
21
21
|
Requires-Dist: platformdirs<5,>=4.10
|
|
22
22
|
Requires-Dist: pydantic<3,>=2.12.5
|
|
23
23
|
Requires-Dist: pyyaml<7,>=6
|
|
@@ -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]]:
|
|
@@ -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}}}'
|
|
@@ -961,8 +961,11 @@ def view_transform_date_diff(invocation: Invocation) -> HandlerResult:
|
|
|
961
961
|
|
|
962
962
|
|
|
963
963
|
def view_transform_delete_columns(invocation: Invocation) -> HandlerResult:
|
|
964
|
-
"""Delete columns. ``columns`` is required."""
|
|
964
|
+
"""Delete columns. ``columns`` is required. Prompt or ``--yes`` required."""
|
|
965
965
|
view_id = _view_id(invocation)
|
|
966
|
+
enforce_confirmation(
|
|
967
|
+
invocation, policy=POLICY_PROMPT_OR_YES, action=f"delete columns on view {view_id}"
|
|
968
|
+
)
|
|
966
969
|
document = invocation.load_input()
|
|
967
970
|
_require_field(document, "columns")
|
|
968
971
|
assert document is not None
|
|
@@ -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"):
|
|
@@ -147,7 +147,15 @@ def column_warnings(
|
|
|
147
147
|
)
|
|
148
148
|
if others:
|
|
149
149
|
shown = ", ".join(repr(o) for o in others[:_MAX_EXAMPLES])
|
|
150
|
-
detail += f" Other values: {shown}
|
|
150
|
+
detail += f" Other values: {shown}"
|
|
151
|
+
if kind == "numbers":
|
|
152
|
+
# A value that fails this number check truly can't
|
|
153
|
+
# become NUMERIC. Dates have no such guarantee: the
|
|
154
|
+
# backend's date parser accepts formats this
|
|
155
|
+
# whitelist doesn't recognize, so claiming data loss
|
|
156
|
+
# there would be untrue.
|
|
157
|
+
detail += " (the conversion makes them empty)"
|
|
158
|
+
detail += "."
|
|
151
159
|
warnings.append(
|
|
152
160
|
{
|
|
153
161
|
"column": column,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mammoth-cli"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.78"
|
|
4
4
|
description = "Command-line interface for the Mammoth Analytics platform"
|
|
5
5
|
authors = [{name = "Mammoth Analytics", email = "support@mammoth.io"}]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "LicenseRef-Proprietary"
|
|
8
8
|
requires-python = ">=3.12,<3.15"
|
|
9
9
|
dependencies = [
|
|
10
|
-
"mammoth-io (>=0.7.
|
|
10
|
+
"mammoth-io (>=0.7.36,<0.8)",
|
|
11
11
|
"typer (>=0.27,<0.28)",
|
|
12
12
|
"rich (>=15,<16)",
|
|
13
13
|
"platformdirs (>=4.10,<5)",
|