mammoth-cli 2.2.46__tar.gz → 2.2.48__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/PKG-INFO +2 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/app.py +15 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +1 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +5 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +1 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +3 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +3 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/auth.py +51 -23
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/job.py +3 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/project.py +2 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/schema.py +5 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/view.py +72 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/view_ops.py +117 -4
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/context/oauth.py +122 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/errors/envelope.py +5 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/dataset_health.py +2 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/invocation.py +1 -0
- mammoth_cli-2.2.48/mammoth_cli/runtime/locked_files.py +70 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/state.py +82 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/job_outcome.py +23 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/mapping.py +60 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/positionals.py +3 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/sdk_service.py +19 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/write_impact.py +77 -8
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/pyproject.toml +2 -2
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/file.yaml +1 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/view.yaml +3 -3
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/sdk-catalog.source.yaml +28 -1
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/sdk-methods.yaml +36 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/.gitignore +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/LICENSE +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/README.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/resolve.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/token.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/calc.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/link.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/resolve.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/view_profile.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/embed.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/confirm.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/dryrun.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/dryrun_targets.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/embedded.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/intent_only.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/new_data.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/session.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/runtime/verify.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/append_blank_columns.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/board_values.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/command_contract.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/conditional_format.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/dashboard_filters.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/dashboard_pages.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/dashboard_review.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/data_profile.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/data_quality.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/input_fields.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/listing.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/local_time.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/read_queries.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/sql_check.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/stored_stats.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/testing.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/text_dates.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/services/view_analysis.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/addon.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/agent.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/ai.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/annotation.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/auth.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/automation.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/batch.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/billing.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/calc.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/client-app.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/completion.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/config.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/connector.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/context.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/dashboard.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/data-app.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/dataset.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/external-key.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/folder.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/link.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/notification.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/parameter.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/project.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/resolve.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/schedule.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/skill.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/snippet.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/support.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/template.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/token.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/trash.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/upgrade.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/user.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/webhook.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/workflow.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/commands/workspace.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/edits-target.source.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/manifests/schema-v1.json +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/spec/openapi/openapi.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mammoth-cli
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.48
|
|
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.9,>=0.8.
|
|
20
|
+
Requires-Dist: mammoth-io<0.9,>=0.8.26
|
|
21
21
|
Requires-Dist: platformdirs<5,>=4.10
|
|
22
22
|
Requires-Dist: pydantic<3,>=2.12.5
|
|
23
23
|
Requires-Dist: pyyaml<7,>=6
|
|
@@ -46,6 +46,7 @@ from mammoth_cli.runtime import executor, validate
|
|
|
46
46
|
from mammoth_cli.runtime.dataset_health import with_dataset_health
|
|
47
47
|
from mammoth_cli.runtime.intent_only import refuse_hand_crafted_write
|
|
48
48
|
from mammoth_cli.runtime.invocation import Invocation
|
|
49
|
+
from mammoth_cli.runtime.locked_files import with_locked_files
|
|
49
50
|
from mammoth_cli.runtime.new_data import with_new_data_path
|
|
50
51
|
from mammoth_cli.runtime.state import with_state
|
|
51
52
|
from mammoth_cli.runtime.strict import validate_extra_args
|
|
@@ -717,6 +718,18 @@ def _shared_option_params() -> list[inspect.Parameter]:
|
|
|
717
718
|
),
|
|
718
719
|
],
|
|
719
720
|
),
|
|
721
|
+
opt(
|
|
722
|
+
"allow_empty",
|
|
723
|
+
False,
|
|
724
|
+
Annotated[
|
|
725
|
+
bool,
|
|
726
|
+
typer.Option(
|
|
727
|
+
"--allow-empty",
|
|
728
|
+
help="Add a keep filter that matches no row; without it the dry run fails.",
|
|
729
|
+
rich_help_panel="Safety",
|
|
730
|
+
),
|
|
731
|
+
],
|
|
732
|
+
),
|
|
720
733
|
opt(
|
|
721
734
|
"input_file",
|
|
722
735
|
None,
|
|
@@ -869,7 +882,8 @@ def _execute(invocation: Invocation) -> None:
|
|
|
869
882
|
if invocation.dry_run:
|
|
870
883
|
return _dry_run(handler, invocation)
|
|
871
884
|
data, meta = handler(invocation)
|
|
872
|
-
|
|
885
|
+
checked = with_locked_files(with_dataset_health(_apply_verify(invocation, data)))
|
|
886
|
+
return with_new_data_path(checked), meta
|
|
873
887
|
|
|
874
888
|
executor.run(
|
|
875
889
|
invocation.command_id,
|
|
@@ -49,7 +49,7 @@ MAMMOTH_OUTPUT=json MAMMOTH_NO_INPUT=1` once.
|
|
|
49
49
|
Use the project you are given; never `project ensure`. No `doctor`, auth,
|
|
50
50
|
profiles, `config set`, `upgrade`, `skill install`, env vars, local files,
|
|
51
51
|
stdin, `--input FILE` or `output_path` (they fail `not_available_embedded`);
|
|
52
|
-
downloads return `download_url`.
|
|
52
|
+
downloads return `download_url`. Ask for a reload on `authentication_failed` only; on a 403 check ids and scope.
|
|
53
53
|
|
|
54
54
|
## Defaults you do not repeat
|
|
55
55
|
|
{mammoth_cli-2.2.46 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md
RENAMED
|
@@ -17,8 +17,11 @@ created without a project.
|
|
|
17
17
|
`mammoth auth login --method oauth` signs the operator in through the browser (PKCE, loopback
|
|
18
18
|
`127.0.0.1`). The CLI keeps a 1 h `mm_` token and refreshes it under a file lock. `auth status`
|
|
19
19
|
reports `credential: oauth` and `expires_at`. A refresh that fails exits 4 with `login_expired`:
|
|
20
|
-
tell the operator to run `mammoth auth login` again. `--no-browser` prints the URL.
|
|
21
|
-
|
|
20
|
+
tell the operator to run `mammoth auth login` again. `--no-browser` prints the URL. `--device` is the sign-in for a
|
|
21
|
+
machine with no browser: it prints a short code and a URL for the operator to approve on another
|
|
22
|
+
device, then polls. Browser and device sign-in are for the operator's own terminal; an agent never
|
|
23
|
+
runs them. A key made with `token create` expires after 30 days: `cli_key_expired` means run
|
|
24
|
+
`mammoth auth login` again. `auth logout` also revokes the
|
|
22
25
|
connection on the server, and warns (still clearing local state) if it cannot.
|
|
23
26
|
|
|
24
27
|
`mammoth token create NAME`, `token list`, `token revoke KEY` are aliases of `client-app create`,
|
|
@@ -738,7 +738,7 @@ Status on release: ran once on CLI 2.0.21 — ergonomics sweep 2026-09-19: exit
|
|
|
738
738
|
|
|
739
739
|
Run: `mammoth view optimize`. Exact input fields: `mammoth schema get view.optimize`.
|
|
740
740
|
|
|
741
|
-
Example: `mammoth view optimize 123
|
|
741
|
+
Example: `mammoth view optimize 123`. Illustrative only: append `--yes` after observing an owned target.
|
|
742
742
|
|
|
743
743
|
Result: `ViewOptimizeResult`; mutation `reversible_pipeline`, confirmation `prompt_or_yes`, wait policy `start_or_wait`.
|
|
744
744
|
|
|
@@ -19,5 +19,6 @@ condition, not permission to retry mutations. The credential rule lives in
|
|
|
19
19
|
Exit 4 is auth/authorization; preserve the structured error and stop safely.
|
|
20
20
|
|
|
21
21
|
In the Mammoth app none of the above applies: there is no `auth status`,
|
|
22
|
-
`doctor` or profile, and the app passes the login and project. On
|
|
23
|
-
the user to reload the page
|
|
22
|
+
`doctor` or profile, and the app passes the login and project. On `authentication_failed` or
|
|
23
|
+
`not_authenticated` ask the user to reload the page; on `authorization_required` (403)
|
|
24
|
+
check the ids and scope first, since a reload does not fix them.
|
|
@@ -7,7 +7,8 @@ these commands are not blanket permission to replay a mutation.
|
|
|
7
7
|
|
|
8
8
|
| exit | error.code (examples) | next step |
|
|
9
9
|
|---|---|---|
|
|
10
|
-
| 4 | not_authenticated, authentication_failed | `mammoth auth login`; in the Mammoth app, ask the user to reload the page |
|
|
10
|
+
| 4 | not_authenticated, authentication_failed | `mammoth auth login`; in the Mammoth app, ask the user to reload the page (an expired or invalid sign-in only) |
|
|
11
|
+
| 4 | authorization_required (403) | check the view, dataset and project ids and the scope first; a reload does not fix a wrong id |
|
|
11
12
|
| 2 | keyring_unavailable, keyring_unresponsive | operator runs `mammoth auth login --storage file` (or unlocks the OS keychain); not seen in the app |
|
|
12
13
|
| 5 | resource_not_found | re-list to find the correct id |
|
|
13
14
|
| 2 | project_required | `mammoth context project use ID` or `--project` (in the app: `--project` only) |
|
|
@@ -48,7 +49,7 @@ the `run_id` when you report a backend fault.
|
|
|
48
49
|
|
|
49
50
|
Do not run the next mutation on any of these; report the state (with
|
|
50
51
|
`log_ref.run_id`) and either recover per the tables above or hand off:
|
|
51
|
-
- `auth status` is missing or its `endpoint` does not match the intended environment (in the app there is no `auth status` or `doctor`:
|
|
52
|
+
- `auth status` is missing or its `endpoint` does not match the intended environment (in the app there is no `auth status` or `doctor`: a sign-in error (`authentication_failed`, `not_authenticated`) means ask the user to reload the page; `authorization_required` means check the ids and scope)
|
|
52
53
|
- `mammoth doctor` fails (CLI only)
|
|
53
54
|
- a dataset is still `need_action` after the settings update
|
|
54
55
|
- a read-back after a value-changing step shows a uniform column (all 0 / all one value / all null) or a changed row the condition should not have touched
|
|
@@ -270,13 +270,13 @@ def _verify_login(invocation: Invocation, *, base_url: str, api_token: str) -> i
|
|
|
270
270
|
return workspace_id
|
|
271
271
|
|
|
272
272
|
|
|
273
|
-
_LOGIN_METHODS = ("oauth", "token")
|
|
273
|
+
_LOGIN_METHODS = ("oauth", "device", "token")
|
|
274
274
|
|
|
275
275
|
|
|
276
276
|
def _choose_method(
|
|
277
277
|
method: str | None, invocation: Invocation, *, server_prefix: str | None, blockers: list[str]
|
|
278
278
|
) -> str:
|
|
279
|
-
"""Pick ``oauth`` or ``token``: explicit, implied by ``--input``, or asked.
|
|
279
|
+
"""Pick ``oauth``, ``device`` or ``token``: explicit, implied by ``--input``, or asked.
|
|
280
280
|
|
|
281
281
|
A server with no CLI OAuth client, a non-interactive run, and ``--input``
|
|
282
282
|
all keep the token login that existed before browser sign-in.
|
|
@@ -289,10 +289,10 @@ def _choose_method(
|
|
|
289
289
|
exit_status=EXIT_USAGE,
|
|
290
290
|
hint=f"Use one of: {', '.join(_LOGIN_METHODS)}.",
|
|
291
291
|
)
|
|
292
|
-
if method
|
|
292
|
+
if method != "token" and invocation.input_file is not None:
|
|
293
293
|
raise CliError(
|
|
294
294
|
code="invalid_argument_combination",
|
|
295
|
-
message="--input is for token logins; it cannot be used with --method
|
|
295
|
+
message=f"--input is for token logins; it cannot be used with --method {method}.",
|
|
296
296
|
exit_status=EXIT_USAGE,
|
|
297
297
|
)
|
|
298
298
|
return method
|
|
@@ -302,16 +302,18 @@ def _choose_method(
|
|
|
302
302
|
return "token"
|
|
303
303
|
typer.echo("How do you want to sign in?", err=True)
|
|
304
304
|
typer.echo(" 1) Browser (OAuth) recommended", err=True)
|
|
305
|
+
typer.echo(" 2) Device code no browser on this machine", err=True)
|
|
305
306
|
typer.echo(" 3) Paste an API token for CI and scripts", err=True)
|
|
306
307
|
choice = typer.prompt("Choose", default="1", show_default=False).strip()
|
|
307
|
-
|
|
308
|
+
methods = {"1": "oauth", "2": "device", "3": "token"}
|
|
309
|
+
if choice not in methods:
|
|
308
310
|
raise CliError(
|
|
309
311
|
code="invalid_login_method",
|
|
310
312
|
message=f"'{choice}' is not one of the choices.",
|
|
311
313
|
exit_status=EXIT_USAGE,
|
|
312
|
-
hint="Enter 1 for the browser or 3 to paste an API token.",
|
|
314
|
+
hint="Enter 1 for the browser, 2 for a device code or 3 to paste an API token.",
|
|
313
315
|
)
|
|
314
|
-
return
|
|
316
|
+
return methods[choice]
|
|
315
317
|
|
|
316
318
|
|
|
317
319
|
def _iso(epoch: int) -> str:
|
|
@@ -325,16 +327,18 @@ def _run_oauth_login(
|
|
|
325
327
|
storage: str,
|
|
326
328
|
open_browser: bool,
|
|
327
329
|
interactive: bool,
|
|
330
|
+
device: bool = False,
|
|
328
331
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
329
|
-
"""Sign in through the browser and persist the refreshable session."""
|
|
332
|
+
"""Sign in through the browser (or a device code) and persist the refreshable session."""
|
|
330
333
|
base_url = resolve_base_url(server_prefix)
|
|
331
334
|
client_id = oauth.client_id_for(server_prefix)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
client_id=client_id,
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
say = lambda line: typer.echo(line, err=True) # noqa: E731
|
|
336
|
+
if device:
|
|
337
|
+
session = oauth.device_login(base_url, client_id=client_id, say=say)
|
|
338
|
+
else:
|
|
339
|
+
session = oauth.browser_login(
|
|
340
|
+
base_url, client_id=client_id, open_browser=open_browser, say=say
|
|
341
|
+
)
|
|
338
342
|
workspace_id = _verify_login(invocation, base_url=base_url, api_token=session.access)
|
|
339
343
|
|
|
340
344
|
profile_name = invocation.profile or profiles.DEFAULT_PROFILE_NAME
|
|
@@ -378,8 +382,18 @@ def _run_login(
|
|
|
378
382
|
storage: str,
|
|
379
383
|
method: str | None = None,
|
|
380
384
|
no_browser: bool = False,
|
|
385
|
+
device: bool = False,
|
|
381
386
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
382
387
|
"""Validate, connection-check, and persist one `auth login` invocation."""
|
|
388
|
+
if device:
|
|
389
|
+
if method not in (None, "device"):
|
|
390
|
+
raise CliError(
|
|
391
|
+
code="invalid_argument_combination",
|
|
392
|
+
message=f"--device cannot be used with --method {method}.",
|
|
393
|
+
exit_status=EXIT_USAGE,
|
|
394
|
+
hint="Use --device alone, or --method device.",
|
|
395
|
+
)
|
|
396
|
+
method = "device"
|
|
383
397
|
if storage not in _STORAGE_MODES:
|
|
384
398
|
raise CliError(
|
|
385
399
|
code="invalid_storage_mode",
|
|
@@ -390,16 +404,15 @@ def _run_login(
|
|
|
390
404
|
|
|
391
405
|
blockers = _prompt_blockers(invocation)
|
|
392
406
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
== "oauth"
|
|
396
|
-
):
|
|
407
|
+
chosen = _choose_method(method, invocation, server_prefix=server_prefix, blockers=blockers)
|
|
408
|
+
if chosen in ("oauth", "device"):
|
|
397
409
|
return _run_oauth_login(
|
|
398
410
|
invocation,
|
|
399
411
|
server_prefix=server_prefix,
|
|
400
412
|
storage=storage,
|
|
401
413
|
open_browser=not no_browser,
|
|
402
414
|
interactive=not blockers,
|
|
415
|
+
device=chosen == "device",
|
|
403
416
|
)
|
|
404
417
|
|
|
405
418
|
if invocation.input_file is not None:
|
|
@@ -488,19 +501,33 @@ def auth_login(
|
|
|
488
501
|
method: str | None = typer.Option(
|
|
489
502
|
None,
|
|
490
503
|
"--method",
|
|
491
|
-
help=
|
|
492
|
-
|
|
504
|
+
help=(
|
|
505
|
+
"How to sign in: a browser (oauth), a code typed on another device (device) "
|
|
506
|
+
"or a pasted API token (token)."
|
|
507
|
+
),
|
|
508
|
+
metavar="oauth|device|token",
|
|
493
509
|
),
|
|
494
510
|
no_browser: bool = typer.Option(
|
|
495
511
|
False, "--no-browser", help="Print the sign-in URL instead of opening a browser."
|
|
496
512
|
),
|
|
513
|
+
device: bool = typer.Option(
|
|
514
|
+
False,
|
|
515
|
+
"--device",
|
|
516
|
+
help=(
|
|
517
|
+
"Sign in with a device code, for a machine with no browser: the CLI prints "
|
|
518
|
+
"a short code and a URL, you approve on any other device, and the CLI "
|
|
519
|
+
"polls until you do. Same as --method device."
|
|
520
|
+
),
|
|
521
|
+
),
|
|
497
522
|
) -> None:
|
|
498
523
|
"""Log in and store one profile's credentials.
|
|
499
524
|
|
|
500
525
|
In a terminal, choose a browser sign-in (OAuth; the CLI keeps a token that
|
|
501
|
-
refreshes itself)
|
|
502
|
-
|
|
503
|
-
(
|
|
526
|
+
refreshes itself), a device code (``--device``: for a machine with no
|
|
527
|
+
browser, you type a short code on any other device) or paste an API token
|
|
528
|
+
(mm_..., from Workspace settings -> API Tokens). The token names its
|
|
529
|
+
workspace. For non-interactive use (agents, CI), pass ``--input FILE`` with
|
|
530
|
+
{"api_token": ...} instead.
|
|
504
531
|
Performs a lightweight connection check before saving anything; a failed
|
|
505
532
|
check leaves existing profile state unchanged.
|
|
506
533
|
"""
|
|
@@ -531,6 +558,7 @@ def auth_login(
|
|
|
531
558
|
storage=storage,
|
|
532
559
|
method=method,
|
|
533
560
|
no_browser=no_browser,
|
|
561
|
+
device=device,
|
|
534
562
|
)
|
|
535
563
|
|
|
536
564
|
executor.run(
|
|
@@ -199,7 +199,9 @@ def _with_outcome(service: Any, data: dict[str, Any]) -> dict[str, Any]:
|
|
|
199
199
|
gets its copied counts and per-view runs (no read). The job succeeded either way, so
|
|
200
200
|
a view that cannot be read is an ``outcome_error``, never a failed wait.
|
|
201
201
|
"""
|
|
202
|
-
|
|
202
|
+
result = job_outcome.with_derived_datasets(data.get("result"))
|
|
203
|
+
data = {**data, "result": result} if result is not data.get("result") else data
|
|
204
|
+
copied = job_outcome.copy_outcome(result)
|
|
203
205
|
if copied is not None and data.get("operation") == job_outcome.COPY_PROJECT_OPERATION:
|
|
204
206
|
return {**data, "outcome": copied}
|
|
205
207
|
target = job_outcome.view_target(data)
|
|
@@ -36,6 +36,7 @@ from mammoth_cli.runtime.confirm import (
|
|
|
36
36
|
)
|
|
37
37
|
from mammoth_cli.runtime.invocation import Invocation
|
|
38
38
|
from mammoth_cli.runtime.session import open_service, resolved_project
|
|
39
|
+
from mammoth_cli.services import job_outcome
|
|
39
40
|
|
|
40
41
|
HandlerResult = tuple[Any, dict[str, Any]]
|
|
41
42
|
|
|
@@ -299,7 +300,7 @@ def project_copy(invocation: Invocation) -> HandlerResult:
|
|
|
299
300
|
_forward_optional(document, kwargs, _COPY_OPTIONAL)
|
|
300
301
|
with open_service(invocation) as (service, auth):
|
|
301
302
|
queued = service.call(_symbol(invocation), **kwargs)
|
|
302
|
-
data = service.wait_if_job(queued)
|
|
303
|
+
data = job_outcome.with_derived_datasets(service.wait_if_job(queued))
|
|
303
304
|
return data, _meta(invocation, auth.workspace_id, source_id)
|
|
304
305
|
|
|
305
306
|
|
|
@@ -45,6 +45,7 @@ _PROTECTED_INPUT_PATH = "/private/path/request.json"
|
|
|
45
45
|
# Commands whose useful example the SDK signature cannot express: a distinct
|
|
46
46
|
# second id, an optional-but-central input field, or two views to compare.
|
|
47
47
|
_FIXED_EXAMPLES: dict[str, tuple[tuple[str, ...], dict[str, Any]]] = {
|
|
48
|
+
"view.optimize": (("123",), {}),
|
|
48
49
|
"project.check": (("123", "456"), {}),
|
|
49
50
|
"view.data.profile": (("123",), {"target": "Churn"}),
|
|
50
51
|
"workflow.canvas": (
|
|
@@ -500,6 +501,10 @@ _COMMAND_DISCOVERY_PURPOSES = {
|
|
|
500
501
|
"connect our own internal custom api build a connector for an unsupported source"
|
|
501
502
|
),
|
|
502
503
|
"file.set-password": "locked password protected file pdf excel unlock",
|
|
504
|
+
"file.update": (
|
|
505
|
+
"pdf extraction method choose select pending tables only all content custom "
|
|
506
|
+
"uploaded pdf file extract_pdf_tables"
|
|
507
|
+
),
|
|
503
508
|
"project.sample-flow": "sample example demo starter data dataset try start from",
|
|
504
509
|
"webhook.create": (
|
|
505
510
|
"webhook http endpoint api push create data pushed in sent from another system receive "
|
|
@@ -23,6 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
import dataclasses
|
|
24
24
|
import inspect
|
|
25
25
|
import shlex
|
|
26
|
+
import time
|
|
26
27
|
from collections.abc import Callable
|
|
27
28
|
from datetime import UTC, datetime
|
|
28
29
|
from decimal import Decimal, InvalidOperation
|
|
@@ -212,6 +213,48 @@ def _resolve_dataset_id(
|
|
|
212
213
|
return dataset_id
|
|
213
214
|
|
|
214
215
|
|
|
216
|
+
def _check_parent_matches(view_id: int, given: int, real: int) -> None:
|
|
217
|
+
"""Refuse a DATASET_ID that is not the view's parent, before any write."""
|
|
218
|
+
if given == real:
|
|
219
|
+
return
|
|
220
|
+
raise CliError(
|
|
221
|
+
code=CODE_INVALID_ARGUMENT,
|
|
222
|
+
message=f"view {view_id} belongs to dataset {real}, not {given}",
|
|
223
|
+
exit_status=EXIT_USAGE,
|
|
224
|
+
hint=f"Run it again with dataset {real}, or leave DATASET_ID out: mammoth looks it up.",
|
|
225
|
+
details={"view_id": view_id, "given_dataset_id": given, "dataset_id": real},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _view_parent_dataset_id(service: Any, invocation: Invocation, view_id: int) -> int:
|
|
230
|
+
"""The view's own dataset: the remembered parent, else one read of the view's row."""
|
|
231
|
+
profile_name = _profile_name(invocation)
|
|
232
|
+
workspace_id = getattr(service, "_workspace_id", None)
|
|
233
|
+
remembered = parents.lookup(profile_name, workspace_id, view_id)
|
|
234
|
+
if remembered is not None:
|
|
235
|
+
return remembered
|
|
236
|
+
dataset_id = int(service.call(_FIND_DATASET_SYMBOL, dataview_id=view_id))
|
|
237
|
+
parents.remember(profile_name, workspace_id, {view_id: dataset_id})
|
|
238
|
+
return dataset_id
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _verified_dataset_id(
|
|
242
|
+
service: Any, invocation: Invocation, view_id: int, document: dict[str, Any]
|
|
243
|
+
) -> int:
|
|
244
|
+
"""The view's dataset for a write, so the caller never has to supply it.
|
|
245
|
+
|
|
246
|
+
A DATASET_ID (positional or ``dataset_id`` input field) is checked against the
|
|
247
|
+
view's real parent and refused when it differs; with none, the parent is looked up.
|
|
248
|
+
"""
|
|
249
|
+
explicit = _int_positional_at(invocation, 1, "dataset id")
|
|
250
|
+
if explicit is None and document.get(_DATASET_ID_FIELD) is not None:
|
|
251
|
+
explicit = _int_field(document[_DATASET_ID_FIELD], _DATASET_ID_FIELD)
|
|
252
|
+
real = _view_parent_dataset_id(service, invocation, view_id)
|
|
253
|
+
if explicit is not None:
|
|
254
|
+
_check_parent_matches(view_id, explicit, real)
|
|
255
|
+
return real
|
|
256
|
+
|
|
257
|
+
|
|
215
258
|
def _resolve_dataset_id_for_settle(
|
|
216
259
|
service: Any,
|
|
217
260
|
invocation: Invocation,
|
|
@@ -1029,7 +1072,7 @@ def view_optimize(invocation: Invocation) -> HandlerResult:
|
|
|
1029
1072
|
kwargs: dict[str, Any] = {"dataview_id": dataview_id, "project_id": project_id}
|
|
1030
1073
|
_forward_optional(document, kwargs, ("apply_rules",))
|
|
1031
1074
|
with open_service(invocation) as (service, auth):
|
|
1032
|
-
kwargs["dataset_id"] =
|
|
1075
|
+
kwargs["dataset_id"] = _verified_dataset_id(service, invocation, dataview_id, document)
|
|
1033
1076
|
data = service.call(_symbol(invocation), **kwargs)
|
|
1034
1077
|
if isinstance(data, dict) and data.get("job_id") is not None:
|
|
1035
1078
|
settled = service.wait_if_job({"job_id": data["job_id"]})
|
|
@@ -4528,6 +4571,29 @@ def wait_for_view_row_count(
|
|
|
4528
4571
|
return rows_after, pipeline_error
|
|
4529
4572
|
|
|
4530
4573
|
|
|
4574
|
+
#: Bounded wait for a submitted draft's data: its steps run after the submit returns.
|
|
4575
|
+
_REFRESH_ATTEMPTS = 40
|
|
4576
|
+
_REFRESH_RETRY_S = 0.5
|
|
4577
|
+
|
|
4578
|
+
|
|
4579
|
+
def wait_for_refreshed_view(
|
|
4580
|
+
service: Any, dataset_id: int, view_id: int, project_id: int | None, stamp_before: Any
|
|
4581
|
+
) -> dict[str, Any] | None:
|
|
4582
|
+
"""The view's record once its ``data_updated_at`` has moved past ``stamp_before``.
|
|
4583
|
+
|
|
4584
|
+
``None`` when it never did within the bound; the caller reports that as
|
|
4585
|
+
unfinished, never as a row count it did not see refreshed.
|
|
4586
|
+
"""
|
|
4587
|
+
for _ in range(_REFRESH_ATTEMPTS):
|
|
4588
|
+
info = service.call(
|
|
4589
|
+
_DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
|
|
4590
|
+
)
|
|
4591
|
+
if isinstance(info, dict) and info.get("data_updated_at") != stamp_before:
|
|
4592
|
+
return info
|
|
4593
|
+
time.sleep(_REFRESH_RETRY_S)
|
|
4594
|
+
return None
|
|
4595
|
+
|
|
4596
|
+
|
|
4531
4597
|
def _row_count_now(service: Any, dataset_id: int, view_id: int, project_id: int | None) -> Any:
|
|
4532
4598
|
"""A view's row count, read directly with no settle wait.
|
|
4533
4599
|
|
|
@@ -4582,7 +4648,11 @@ def _settle_async_view_write(
|
|
|
4582
4648
|
return data
|
|
4583
4649
|
dataset_id = int(dataset_id)
|
|
4584
4650
|
row_check: dict[str, Any] = dict(before or {"rows_before": None})
|
|
4585
|
-
|
|
4651
|
+
# The response names the follow-on job that runs the pipeline; read back
|
|
4652
|
+
# before it ends and the view still shows the old row count (FB-05).
|
|
4653
|
+
rows_after, pipeline_error = wait_for_view_row_count(
|
|
4654
|
+
service, dataset_id, view_id, project_id, write_result=data
|
|
4655
|
+
)
|
|
4586
4656
|
row_check["rows_after"] = rows_after
|
|
4587
4657
|
data = {**data, "row_check": row_check}
|
|
4588
4658
|
if pipeline_error is not None:
|
|
@@ -20,6 +20,7 @@ enum-typed fields are forwarded as the plain string given on ``--input``.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import json
|
|
23
|
+
import re
|
|
23
24
|
import shlex
|
|
24
25
|
from collections.abc import Callable
|
|
25
26
|
from datetime import UTC, datetime, timedelta
|
|
@@ -29,6 +30,7 @@ from mammoth_cli.commands.view import (
|
|
|
29
30
|
_DATAVIEW_GET_SYMBOL,
|
|
30
31
|
_FIND_DATASET_SYMBOL,
|
|
31
32
|
BRIEF_VIEW_FIELDS,
|
|
33
|
+
UNFINISHED_STATE,
|
|
32
34
|
_dataview_metadata,
|
|
33
35
|
_require_discovery_allowed,
|
|
34
36
|
apply_column_renames,
|
|
@@ -39,6 +41,7 @@ from mammoth_cli.commands.view import (
|
|
|
39
41
|
reject_dataset_export_conflicts,
|
|
40
42
|
wait_for_followon_job,
|
|
41
43
|
wait_for_pipeline_to_settle,
|
|
44
|
+
wait_for_refreshed_view,
|
|
42
45
|
wait_for_view_row_count,
|
|
43
46
|
with_join_check,
|
|
44
47
|
)
|
|
@@ -60,12 +63,13 @@ from mammoth_cli.runtime.dryrun import DryRunStop
|
|
|
60
63
|
from mammoth_cli.runtime.invocation import Invocation
|
|
61
64
|
from mammoth_cli.runtime.session import open_service, resolved_project
|
|
62
65
|
from mammoth_cli.services.command_contract import bind_command_inputs
|
|
63
|
-
from mammoth_cli.services.conditions import CONDITION_KWARG
|
|
66
|
+
from mammoth_cli.services.conditions import CONDITION_KWARG, compile_condition
|
|
64
67
|
from mammoth_cli.services.input_fields import TASK_COUNT_FIELD
|
|
65
68
|
from mammoth_cli.services.sql_check import check_sql_binds
|
|
66
69
|
from mammoth_cli.services.write_impact import (
|
|
67
70
|
ImpactRead,
|
|
68
71
|
Measure,
|
|
72
|
+
empties_view_error,
|
|
69
73
|
measure_bulk_replace,
|
|
70
74
|
measure_duplicates,
|
|
71
75
|
measure_fill_missing,
|
|
@@ -438,6 +442,7 @@ def _flag_unsettled_pipeline(service: Any, dataset_id: int, view_id: int, data:
|
|
|
438
442
|
|
|
439
443
|
|
|
440
444
|
CODE_PIPELINE_CHANGED = "pipeline_changed"
|
|
445
|
+
_DRAFT_STATUS_SYMBOL = "mammoth.api.pipeline.PipelineAPI.get_draft_status"
|
|
441
446
|
_TASK_LIST_SYMBOL = "mammoth.api.pipeline.PipelineAPI.list_tasks"
|
|
442
447
|
|
|
443
448
|
|
|
@@ -911,7 +916,62 @@ def view_draft_status(invocation: Invocation) -> HandlerResult:
|
|
|
911
916
|
def view_draft_submit(invocation: Invocation) -> HandlerResult:
|
|
912
917
|
"""Submit a view's draft pipeline changes."""
|
|
913
918
|
view_id = _view_id(invocation)
|
|
914
|
-
|
|
919
|
+
project_id = invocation.project
|
|
920
|
+
|
|
921
|
+
def before(service: Any, dataset_id: int) -> dict[str, Any]:
|
|
922
|
+
return _draft_before_submit(service, dataset_id, view_id, project_id)
|
|
923
|
+
|
|
924
|
+
def after(service: Any, dataset_id: int, state: Any, data: Any) -> Any:
|
|
925
|
+
return _draft_submit_row_check(service, dataset_id, view_id, project_id, state, data)
|
|
926
|
+
|
|
927
|
+
return _dispatch_view(invocation, view_id, "submit_draft", before=before, after=after)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def _draft_before_submit(
|
|
931
|
+
service: Any, dataset_id: int, view_id: int, project_id: int | None
|
|
932
|
+
) -> dict[str, Any]:
|
|
933
|
+
"""The view's row count and data timestamp, and whether its draft has changes to run."""
|
|
934
|
+
info = service.call(
|
|
935
|
+
_DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
|
|
936
|
+
)
|
|
937
|
+
draft = service.call(_DRAFT_STATUS_SYMBOL, dataview_id=view_id)
|
|
938
|
+
return {
|
|
939
|
+
"row_count": info.get("row_count"),
|
|
940
|
+
"stamp": info.get("data_updated_at"),
|
|
941
|
+
"pending": bool(draft.get("has_pending_changes")),
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def _draft_submit_row_check(
|
|
946
|
+
service: Any,
|
|
947
|
+
dataset_id: int,
|
|
948
|
+
view_id: int,
|
|
949
|
+
project_id: int | None,
|
|
950
|
+
before: dict[str, Any],
|
|
951
|
+
data: Any,
|
|
952
|
+
) -> Any:
|
|
953
|
+
"""``data`` with the row count read once the submitted steps' data is in.
|
|
954
|
+
|
|
955
|
+
The submit returns with the pipeline ``idle`` while its steps still run, so a
|
|
956
|
+
count read at once is the old one (FB-22). A draft with nothing to run is
|
|
957
|
+
read at once; data that never refreshes is reported as unfinished.
|
|
958
|
+
"""
|
|
959
|
+
if not isinstance(data, dict):
|
|
960
|
+
return data
|
|
961
|
+
if before["pending"]:
|
|
962
|
+
info = wait_for_refreshed_view(service, dataset_id, view_id, project_id, before["stamp"])
|
|
963
|
+
else:
|
|
964
|
+
info = service.call(
|
|
965
|
+
_DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
|
|
966
|
+
)
|
|
967
|
+
rows_after = info.get("row_count") if info is not None else None
|
|
968
|
+
out = {**data, "row_check": {"rows_before": before["row_count"], "rows_after": rows_after}}
|
|
969
|
+
if info is None:
|
|
970
|
+
out["pipeline_error"] = {
|
|
971
|
+
"execution_state": UNFINISHED_STATE,
|
|
972
|
+
"wait_error": "the view's data was not refreshed after the submit; read it again",
|
|
973
|
+
}
|
|
974
|
+
return out
|
|
915
975
|
|
|
916
976
|
|
|
917
977
|
def view_draft_discard(invocation: Invocation) -> HandlerResult:
|
|
@@ -1161,7 +1221,8 @@ def _impact_check(
|
|
|
1161
1221
|
|
|
1162
1222
|
A count of zero is a no-op: ``--dry-run`` fails with ``no_op`` and a real run
|
|
1163
1223
|
adds no task (``status: no_change``, like a same-type convert). Otherwise a
|
|
1164
|
-
dry run reports the count as ``predicted_impact``. A
|
|
1224
|
+
dry run reports the count as ``predicted_impact``. A keep filter no row matches
|
|
1225
|
+
fails a dry run with ``empties_view`` unless ``--allow-empty`` is set. A count that cannot run
|
|
1165
1226
|
is reported as unchecked in a dry run, never as zero, and never blocks a write.
|
|
1166
1227
|
"""
|
|
1167
1228
|
|
|
@@ -1187,6 +1248,8 @@ def _impact_check(
|
|
|
1187
1248
|
invocation, "predicted_impact", {"checked": False, "reason": exc.message}
|
|
1188
1249
|
)
|
|
1189
1250
|
return None
|
|
1251
|
+
if measured.empties_message and invocation.dry_run and not invocation.allow_empty:
|
|
1252
|
+
raise empties_view_error(measured.empties_message, view_id=view_id)
|
|
1190
1253
|
if measured.changes == 0:
|
|
1191
1254
|
if invocation.dry_run:
|
|
1192
1255
|
raise no_op_error(measured.no_op_message, view_id=view_id)
|
|
@@ -1386,7 +1449,57 @@ def view_transform_math(invocation: Invocation) -> HandlerResult:
|
|
|
1386
1449
|
assert document is not None
|
|
1387
1450
|
_require_one_destination(document, invocation.command_id)
|
|
1388
1451
|
kwargs = _bind_transform_inputs(invocation, document)
|
|
1389
|
-
|
|
1452
|
+
blanks: dict[str, int] = {}
|
|
1453
|
+
|
|
1454
|
+
def count_blank_inputs(service: Any, dataset_id: int, _kwargs: dict[str, Any]) -> None:
|
|
1455
|
+
blanks.update(
|
|
1456
|
+
_blank_math_inputs(
|
|
1457
|
+
service, dataset_id, view_id, resolved_project(invocation), document["expression"]
|
|
1458
|
+
)
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
data, meta = _dispatch_view(invocation, view_id, "math", prepare=count_blank_inputs, **kwargs)
|
|
1462
|
+
return _with_blank_input_note(data, blanks), meta
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def _blank_math_inputs(
|
|
1466
|
+
service: Any, dataset_id: int, view_id: int, project_id: int | None, expression: Any
|
|
1467
|
+
) -> dict[str, int]:
|
|
1468
|
+
"""Blank cells of each column a math expression names, for the columns that have any.
|
|
1469
|
+
|
|
1470
|
+
Mammoth computes a blank input as 0, so ``qty * unit_price`` gives 0 for an
|
|
1471
|
+
unpriced row and a later total counts it with no warning (FB-01).
|
|
1472
|
+
"""
|
|
1473
|
+
info = service.call(
|
|
1474
|
+
_DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
|
|
1475
|
+
)
|
|
1476
|
+
read = ImpactRead(service, dataset_id, view_id, project_id, apply_column_renames(info))
|
|
1477
|
+
text = str(expression)
|
|
1478
|
+
named = [
|
|
1479
|
+
name
|
|
1480
|
+
for name in read.display_to_internal()
|
|
1481
|
+
if re.search(rf"(?<![\w]){re.escape(name)}(?![\w])", text)
|
|
1482
|
+
]
|
|
1483
|
+
try:
|
|
1484
|
+
counts = {
|
|
1485
|
+
name: read.count(compile_condition({"column": name, "operator": "IS_EMPTY"}))
|
|
1486
|
+
for name in named
|
|
1487
|
+
}
|
|
1488
|
+
except CliError:
|
|
1489
|
+
return {} # A count that cannot run never blocks the write; it only drops the note.
|
|
1490
|
+
return {name: count for name, count in counts.items() if count}
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
def _with_blank_input_note(data: Any, blanks: dict[str, int]) -> Any:
|
|
1494
|
+
"""``data`` with a ``blank_inputs`` note when a math input column has blanks."""
|
|
1495
|
+
if not blanks or not isinstance(data, dict):
|
|
1496
|
+
return data
|
|
1497
|
+
listed = ", ".join(f"{name} is blank in {count} row(s)" for name, count in blanks.items())
|
|
1498
|
+
note = (
|
|
1499
|
+
f"A blank input counts as 0 in this math step: {listed}, so the result there is "
|
|
1500
|
+
"computed as if it were 0. Fill or filter those rows first if a blank must not count as 0."
|
|
1501
|
+
)
|
|
1502
|
+
return {**data, "blank_inputs": {"columns": blanks, "note": note}}
|
|
1390
1503
|
|
|
1391
1504
|
|
|
1392
1505
|
def view_transform_pivot(invocation: Invocation) -> HandlerResult:
|