mammoth-cli 2.2.45__tar.gz → 2.2.47__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.45 → mammoth_cli-2.2.47}/PKG-INFO +2 -2
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/app.py +15 -1
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/schema.py +4 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/view.py +29 -1
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/view_ops.py +117 -4
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/errors/envelope.py +2 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/invocation.py +1 -0
- mammoth_cli-2.2.47/mammoth_cli/runtime/locked_files.py +70 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/state.py +82 -2
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/mapping.py +38 -1
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/sdk_service.py +19 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/write_impact.py +77 -8
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/pyproject.toml +2 -2
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/file.yaml +1 -1
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/sdk-catalog.source.yaml +28 -1
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/sdk-methods.yaml +48 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/.gitignore +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/LICENSE +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/README.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/resolve.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/token.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/auth.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/calc.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/job.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/link.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/project.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/resolve.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/view_profile.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/context/oauth.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/embed.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/confirm.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/dataset_health.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/dryrun.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/dryrun_targets.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/embedded.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/intent_only.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/new_data.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/session.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/runtime/verify.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/append_blank_columns.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/board_values.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/command_contract.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/conditional_format.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/dashboard_filters.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/dashboard_pages.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/dashboard_review.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/data_profile.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/data_quality.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/input_fields.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/job_outcome.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/listing.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/local_time.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/positionals.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/read_queries.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/sql_check.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/stored_stats.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/testing.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/text_dates.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/services/view_analysis.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/addon.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/agent.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/ai.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/annotation.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/auth.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/automation.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/batch.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/billing.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/calc.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/client-app.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/completion.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/config.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/connector.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/context.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/dashboard.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/data-app.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/dataset.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/external-key.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/folder.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/link.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/notification.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/parameter.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/project.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/resolve.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/schedule.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/skill.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/snippet.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/support.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/template.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/token.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/trash.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/upgrade.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/user.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/view.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/webhook.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/workflow.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/commands/workspace.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/edits-target.source.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/manifests/schema-v1.json +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.2.45 → mammoth_cli-2.2.47}/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.47
|
|
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.25
|
|
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,
|
|
@@ -500,6 +500,10 @@ _COMMAND_DISCOVERY_PURPOSES = {
|
|
|
500
500
|
"connect our own internal custom api build a connector for an unsupported source"
|
|
501
501
|
),
|
|
502
502
|
"file.set-password": "locked password protected file pdf excel unlock",
|
|
503
|
+
"file.update": (
|
|
504
|
+
"pdf extraction method choose select pending tables only all content custom "
|
|
505
|
+
"uploaded pdf file extract_pdf_tables"
|
|
506
|
+
),
|
|
503
507
|
"project.sample-flow": "sample example demo starter data dataset try start from",
|
|
504
508
|
"webhook.create": (
|
|
505
509
|
"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
|
|
@@ -4528,6 +4529,29 @@ def wait_for_view_row_count(
|
|
|
4528
4529
|
return rows_after, pipeline_error
|
|
4529
4530
|
|
|
4530
4531
|
|
|
4532
|
+
#: Bounded wait for a submitted draft's data: its steps run after the submit returns.
|
|
4533
|
+
_REFRESH_ATTEMPTS = 40
|
|
4534
|
+
_REFRESH_RETRY_S = 0.5
|
|
4535
|
+
|
|
4536
|
+
|
|
4537
|
+
def wait_for_refreshed_view(
|
|
4538
|
+
service: Any, dataset_id: int, view_id: int, project_id: int | None, stamp_before: Any
|
|
4539
|
+
) -> dict[str, Any] | None:
|
|
4540
|
+
"""The view's record once its ``data_updated_at`` has moved past ``stamp_before``.
|
|
4541
|
+
|
|
4542
|
+
``None`` when it never did within the bound; the caller reports that as
|
|
4543
|
+
unfinished, never as a row count it did not see refreshed.
|
|
4544
|
+
"""
|
|
4545
|
+
for _ in range(_REFRESH_ATTEMPTS):
|
|
4546
|
+
info = service.call(
|
|
4547
|
+
_DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
|
|
4548
|
+
)
|
|
4549
|
+
if isinstance(info, dict) and info.get("data_updated_at") != stamp_before:
|
|
4550
|
+
return info
|
|
4551
|
+
time.sleep(_REFRESH_RETRY_S)
|
|
4552
|
+
return None
|
|
4553
|
+
|
|
4554
|
+
|
|
4531
4555
|
def _row_count_now(service: Any, dataset_id: int, view_id: int, project_id: int | None) -> Any:
|
|
4532
4556
|
"""A view's row count, read directly with no settle wait.
|
|
4533
4557
|
|
|
@@ -4582,7 +4606,11 @@ def _settle_async_view_write(
|
|
|
4582
4606
|
return data
|
|
4583
4607
|
dataset_id = int(dataset_id)
|
|
4584
4608
|
row_check: dict[str, Any] = dict(before or {"rows_before": None})
|
|
4585
|
-
|
|
4609
|
+
# The response names the follow-on job that runs the pipeline; read back
|
|
4610
|
+
# before it ends and the view still shows the old row count (FB-05).
|
|
4611
|
+
rows_after, pipeline_error = wait_for_view_row_count(
|
|
4612
|
+
service, dataset_id, view_id, project_id, write_result=data
|
|
4613
|
+
)
|
|
4586
4614
|
row_check["rows_after"] = rows_after
|
|
4587
4615
|
data = {**data, "row_check": row_check}
|
|
4588
4616
|
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:
|
|
@@ -58,6 +58,7 @@ CODE_INTERRUPTED = "interrupted"
|
|
|
58
58
|
CODE_UNSUPPORTED_CONTRACT = "unsupported_contract"
|
|
59
59
|
CODE_NO_OP = "no_op"
|
|
60
60
|
CODE_WOULD_FAIL = "would_fail"
|
|
61
|
+
CODE_EMPTIES_VIEW = "empties_view"
|
|
61
62
|
|
|
62
63
|
# User-safe, one-line summaries shown to the customer instead of the model-facing
|
|
63
64
|
# ``message``/``hint``. Plain language only: no command names, ids, field names
|
|
@@ -102,6 +103,7 @@ ERROR_SUMMARIES: dict[str, str] = {
|
|
|
102
103
|
"download_failed": "A download didn't complete.",
|
|
103
104
|
"dry_run_unsupported": "That command can't be previewed, so it was not run.",
|
|
104
105
|
"duplicate_input_key": _S_BAD_INPUT,
|
|
106
|
+
"empties_view": "No row matches, so this step would leave the view empty.",
|
|
105
107
|
"empty_search_query": "The search was empty.",
|
|
106
108
|
"export_already_exists": "An export with that name already exists.",
|
|
107
109
|
"incomplete_environment_auth": _S_SIGN_IN,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Password-protected files, stated wherever one appears in a result.
|
|
2
|
+
|
|
3
|
+
A file record carries ``password_protected: true``, but the flag alone does not
|
|
4
|
+
say what to do. :func:`with_locked_files` adds a ``locked_files`` entry per such
|
|
5
|
+
file that says the file's password unlocks it and names the command that takes
|
|
6
|
+
the password. It runs on every result and never makes a request.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
#: Nesting to search: a result, a listing inside it, a record in the listing.
|
|
15
|
+
_MAX_DEPTH = 3
|
|
16
|
+
#: Where a file record keeps the flag: a file listing, a browse search record.
|
|
17
|
+
_INFO_KEYS = ("additional_info", "additional_data")
|
|
18
|
+
_BROWSE_FILE_TYPE = "file_object"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def with_locked_files(data: Any) -> Any:
|
|
22
|
+
"""Return ``data`` with a ``locked_files`` list, or unchanged when no file is locked."""
|
|
23
|
+
# A file listing comes back as an SDK model; it is read as the output prints it.
|
|
24
|
+
plain = data.model_dump(mode="python") if hasattr(data, "model_dump") else data
|
|
25
|
+
if not isinstance(plain, dict):
|
|
26
|
+
return data
|
|
27
|
+
locked = [_entry(record) for record in _locked_records(plain, 0)]
|
|
28
|
+
if not locked:
|
|
29
|
+
return data
|
|
30
|
+
return {**plain, "locked_files": locked}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _locked_records(value: Any, depth: int) -> Iterator[dict[str, Any]]:
|
|
34
|
+
if depth > _MAX_DEPTH:
|
|
35
|
+
return
|
|
36
|
+
if isinstance(value, dict):
|
|
37
|
+
if _is_locked(value) and _file_id(value) is not None:
|
|
38
|
+
yield value
|
|
39
|
+
for child in value.values():
|
|
40
|
+
yield from _locked_records(child, depth + 1)
|
|
41
|
+
elif isinstance(value, list):
|
|
42
|
+
for child in value:
|
|
43
|
+
yield from _locked_records(child, depth + 1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _is_locked(record: dict[str, Any]) -> bool:
|
|
47
|
+
return any(
|
|
48
|
+
isinstance(info := record.get(key), dict) and info.get("password_protected") is True
|
|
49
|
+
for key in _INFO_KEYS
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _file_id(record: dict[str, Any]) -> int | None:
|
|
54
|
+
"""A file record names itself ``id``; a browse record names the file ``object_id``."""
|
|
55
|
+
key = "object_id" if record.get("resource_type") == _BROWSE_FILE_TYPE else "id"
|
|
56
|
+
value = record.get(key)
|
|
57
|
+
return value if isinstance(value, int) else None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _entry(record: dict[str, Any]) -> dict[str, Any]:
|
|
61
|
+
file_id = _file_id(record)
|
|
62
|
+
return {
|
|
63
|
+
"file_id": file_id,
|
|
64
|
+
"name": record.get("name"),
|
|
65
|
+
"detail": "This file is password-protected; its data cannot be read until it is unlocked.",
|
|
66
|
+
"fix": (
|
|
67
|
+
"Ask the user for the file's password, not for an unlocked copy, then run "
|
|
68
|
+
f"mammoth file set-password {file_id} with the password in --input."
|
|
69
|
+
),
|
|
70
|
+
}
|
|
@@ -18,6 +18,7 @@ See ``CONTRACT-write-state.md`` (D-077) for the manifest shape this reads.
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import json
|
|
21
|
+
import time
|
|
21
22
|
from typing import Any
|
|
22
23
|
|
|
23
24
|
from mammoth_cli.errors.envelope import CliError
|
|
@@ -45,6 +46,19 @@ _VIEW_LIST_COMMAND = "view.list"
|
|
|
45
46
|
#: ``verify.state`` of a write that was accepted but has not finished.
|
|
46
47
|
_UNFINISHED_VERIFY_STATE = "processing"
|
|
47
48
|
|
|
49
|
+
_VIEW_TASK_LIST_COMMAND = "view.task.list"
|
|
50
|
+
|
|
51
|
+
#: A write answered ``status: staged`` (auto-run off) put its step in a draft.
|
|
52
|
+
_STAGED_STATUS = "staged"
|
|
53
|
+
_STAGED_DETAIL = (
|
|
54
|
+
"Staged in the draft, not run: the live view is unchanged until 'view draft submit'."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
#: The backend's answer to a data read of a step that has not produced data yet.
|
|
58
|
+
_STEP_NOT_RUN_YET = "no data for pipeline step"
|
|
59
|
+
_DATA_READ_ATTEMPTS = 6
|
|
60
|
+
_DATA_READ_RETRY_S = 2.0
|
|
61
|
+
|
|
48
62
|
|
|
49
63
|
def with_state(invocation: Invocation, data: Any) -> Any:
|
|
50
64
|
"""Return ``data`` with a ``state`` read-back block added, per the manifest.
|
|
@@ -60,7 +74,15 @@ def with_state(invocation: Invocation, data: Any) -> Any:
|
|
|
60
74
|
if not isinstance(readback, dict):
|
|
61
75
|
return data
|
|
62
76
|
unfinished = _unfinished_state(data)
|
|
63
|
-
|
|
77
|
+
if (
|
|
78
|
+
unfinished is None
|
|
79
|
+
and data.get("status") == _STAGED_STATUS
|
|
80
|
+
and readback.get("kind") == "data"
|
|
81
|
+
):
|
|
82
|
+
built = _staged_state(invocation, data, readback)
|
|
83
|
+
else:
|
|
84
|
+
built = unfinished or _build_state(invocation, data, readback)
|
|
85
|
+
state = _enforce_cap(built)
|
|
64
86
|
return {**_unverified_if_unreadable(data, state), "state": state}
|
|
65
87
|
|
|
66
88
|
|
|
@@ -321,7 +343,7 @@ def _data_state(resolved_ids: dict[str, Any], base: Invocation) -> dict[str, Any
|
|
|
321
343
|
if dataset_id is not None:
|
|
322
344
|
view_ids["dataset_id"] = dataset_id
|
|
323
345
|
info = _call_read(_VIEW_GET_COMMAND, view_ids, base)
|
|
324
|
-
page =
|
|
346
|
+
page = _read_data_once_run(view_ids, base)
|
|
325
347
|
rows = page.get("data") if isinstance(page, dict) else None
|
|
326
348
|
sample = [_trim_row(row) for row in (rows or [])[:_SAMPLE_ROW_CAP]]
|
|
327
349
|
state: dict[str, Any] = {
|
|
@@ -338,6 +360,64 @@ def _data_state(resolved_ids: dict[str, Any], base: Invocation) -> dict[str, Any
|
|
|
338
360
|
return state
|
|
339
361
|
|
|
340
362
|
|
|
363
|
+
def _read_data_once_run(view_ids: dict[str, Any], base: Invocation) -> Any:
|
|
364
|
+
"""The view's data, waiting (bounded) for a step that has not run yet.
|
|
365
|
+
|
|
366
|
+
Read right after a write, the new step may have no data yet; the read
|
|
367
|
+
fails with "no data for pipeline step N ... yet" though the step runs a
|
|
368
|
+
moment later (FB-18, FB-22). Any other failure is raised at once.
|
|
369
|
+
"""
|
|
370
|
+
for attempt in range(_DATA_READ_ATTEMPTS):
|
|
371
|
+
try:
|
|
372
|
+
return _call_read(_VIEW_DATA_COMMAND, view_ids, base)
|
|
373
|
+
except CliError as exc:
|
|
374
|
+
if _STEP_NOT_RUN_YET not in exc.message or attempt == _DATA_READ_ATTEMPTS - 1:
|
|
375
|
+
raise
|
|
376
|
+
time.sleep(_DATA_READ_RETRY_S)
|
|
377
|
+
raise AssertionError("unreachable")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _staged_state(
|
|
381
|
+
invocation: Invocation, data: dict[str, Any], readback: dict[str, Any]
|
|
382
|
+
) -> dict[str, Any]:
|
|
383
|
+
"""A draft write's read-back: the view's steps, and that the staged ones have not run.
|
|
384
|
+
|
|
385
|
+
A staged step never ran, so its data cannot be read (FB-20: the agent got
|
|
386
|
+
``unreadable`` and polled four read commands to learn what it had staged).
|
|
387
|
+
"""
|
|
388
|
+
ids_spec: dict[str, Any] = readback.get("ids") or {}
|
|
389
|
+
resolved = {name: _resolve_source(str(src), data, invocation) for name, src in ids_spec.items()}
|
|
390
|
+
view_id = _as_int(resolved.get("view_id") or resolved.get("dataview_id"))
|
|
391
|
+
read_by = f"{_VIEW_TASK_LIST_COMMAND} {view_id}"
|
|
392
|
+
try:
|
|
393
|
+
if view_id is None:
|
|
394
|
+
raise ValueError("a staged write's read-back needs the view id")
|
|
395
|
+
info = _call_read(_VIEW_GET_COMMAND, {"view_id": view_id}, invocation)
|
|
396
|
+
tasks = _call_read(_VIEW_TASK_LIST_COMMAND, {"dataview_id": view_id}, invocation)
|
|
397
|
+
except Exception as exc: # noqa: BLE001 -- a read failure must surface, never crash the write
|
|
398
|
+
reason = exc.message if isinstance(exc, CliError) else str(exc)
|
|
399
|
+
return {"kind": "unreadable", "read_by": read_by, "reason": reason or type(exc).__name__}
|
|
400
|
+
return {
|
|
401
|
+
"kind": "staged",
|
|
402
|
+
"read_by": read_by,
|
|
403
|
+
"detail": _STAGED_DETAIL,
|
|
404
|
+
"live_row_count": info.get("row_count") if isinstance(info, dict) else None,
|
|
405
|
+
"steps": [_step_summary(task) for task in _tasks_of(tasks)],
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _tasks_of(read_data: Any) -> list[dict[str, Any]]:
|
|
410
|
+
tasks = read_data.get("tasks") if isinstance(read_data, dict) else read_data
|
|
411
|
+
return [task for task in tasks or [] if isinstance(task, dict)]
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _step_summary(task: dict[str, Any]) -> dict[str, Any]:
|
|
415
|
+
params = task.get("params")
|
|
416
|
+
if not isinstance(params, dict):
|
|
417
|
+
params = {}
|
|
418
|
+
return {"id": task.get("id"), "task": params.get("TASK_KEY"), "status": task.get("status")}
|
|
419
|
+
|
|
420
|
+
|
|
341
421
|
def changed_columns(document: Any) -> list[str]:
|
|
342
422
|
"""Names of the columns a view edit's input says it wrote."""
|
|
343
423
|
if not isinstance(document, dict):
|
|
@@ -233,6 +233,29 @@ def running_handle(error: CliError, command_id: str) -> dict[str, Any] | None:
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
|
|
236
|
+
def _retry_hint(retry_after: str | None) -> str:
|
|
237
|
+
"""How to retry a read that failed for a transient reason."""
|
|
238
|
+
if retry_after:
|
|
239
|
+
return f"Retry the read after {retry_after} s, as the server's Retry-After asks."
|
|
240
|
+
return "Retry the read once; it changed nothing. If it fails again, tell the user."
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
#: A backend error ``name`` that refuses an action for lack of permission
|
|
244
|
+
#: (``DASHBOARD_CREATE_NOT_ALLOWED``) arrives as a 400, not a 403.
|
|
245
|
+
_NOT_ALLOWED_SUFFIX = "_NOT_ALLOWED"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _not_allowed_reason(body: Any, status: int | None) -> str | None:
|
|
249
|
+
"""The backend's own reason when a 4xx refuses an action as not allowed."""
|
|
250
|
+
if status is None or not 400 <= status < 500 or not isinstance(body, dict):
|
|
251
|
+
return None
|
|
252
|
+
name = body.get("name")
|
|
253
|
+
message = body.get("message")
|
|
254
|
+
if not isinstance(name, str) or not name.endswith(_NOT_ALLOWED_SUFFIX):
|
|
255
|
+
return None
|
|
256
|
+
return message if isinstance(message, str) and message else name
|
|
257
|
+
|
|
258
|
+
|
|
236
259
|
CODE_CLI_KEYS_NOT_ALLOWED = "cli_keys_not_allowed"
|
|
237
260
|
CODE_CLI_KEYS_NEED_BROWSER_SIGN_IN = "cli_keys_need_browser_sign_in"
|
|
238
261
|
_TOKEN_REFUSED_NAME = "INVALID_TOKEN_FOR_CLIENT_APPS" # noqa: S105 - an error name
|
|
@@ -521,6 +544,20 @@ def map_sdk_exception(
|
|
|
521
544
|
request_id=request_id,
|
|
522
545
|
authorization_required=True,
|
|
523
546
|
)
|
|
547
|
+
not_allowed = _not_allowed_reason(exc.response_body, status)
|
|
548
|
+
if not_allowed is not None:
|
|
549
|
+
return CliError(
|
|
550
|
+
code=CODE_AUTHORIZATION_REQUIRED,
|
|
551
|
+
message=not_allowed,
|
|
552
|
+
exit_status=EXIT_AUTH,
|
|
553
|
+
hint=(
|
|
554
|
+
"This user lacks the permission for this action here. Do not retry or "
|
|
555
|
+
"work around it: tell the user what is blocked and who can grant it."
|
|
556
|
+
),
|
|
557
|
+
details=details,
|
|
558
|
+
request_id=request_id,
|
|
559
|
+
authorization_required=True,
|
|
560
|
+
)
|
|
524
561
|
if status == 413:
|
|
525
562
|
# The ingress in front of the API caps a request body (measured
|
|
526
563
|
# on release 2026-09-19: a 16 MB upload passes, 60 MB does not);
|
|
@@ -553,7 +590,7 @@ def map_sdk_exception(
|
|
|
553
590
|
code=CODE_RETRYABLE,
|
|
554
591
|
message="Mammoth is temporarily unavailable or the request timed out.",
|
|
555
592
|
exit_status=EXIT_RETRYABLE,
|
|
556
|
-
hint=
|
|
593
|
+
hint=_retry_hint(exc.retry_after),
|
|
557
594
|
details=details,
|
|
558
595
|
request_id=request_id,
|
|
559
596
|
retryable=True,
|
|
@@ -20,6 +20,7 @@ import os
|
|
|
20
20
|
import re
|
|
21
21
|
import threading
|
|
22
22
|
from collections.abc import Callable, Sequence
|
|
23
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
23
24
|
from types import TracebackType
|
|
24
25
|
from typing import Any, NoReturn
|
|
25
26
|
|
|
@@ -74,6 +75,15 @@ def _unrecognized_expression_token(message: str, expression: Any) -> str:
|
|
|
74
75
|
return token or expression
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def _loop_running_here() -> bool:
|
|
79
|
+
"""Whether the calling thread is already inside a running event loop."""
|
|
80
|
+
try:
|
|
81
|
+
asyncio.get_running_loop()
|
|
82
|
+
except RuntimeError:
|
|
83
|
+
return False
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
|
|
77
87
|
AGENT_RUN_ENV = "MAMMOTH_AGENT_RUN"
|
|
78
88
|
AGENT_RUN_HEADER = "X-Mammoth-Agent-Run"
|
|
79
89
|
|
|
@@ -211,6 +221,15 @@ class SdkMammothService:
|
|
|
211
221
|
"""
|
|
212
222
|
if not inspect.isawaitable(work):
|
|
213
223
|
return work
|
|
224
|
+
if _loop_running_here():
|
|
225
|
+
# An async caller (the agent eval harness) already runs a loop on this
|
|
226
|
+
# thread, and a thread runs one loop at a time: run ours on a helper.
|
|
227
|
+
with ThreadPoolExecutor(max_workers=1) as helper:
|
|
228
|
+
return helper.submit(self._run_on_own_loop, work).result()
|
|
229
|
+
return self._run_on_own_loop(work)
|
|
230
|
+
|
|
231
|
+
def _run_on_own_loop(self, work: Any) -> Any:
|
|
232
|
+
"""Run ``work`` on the service's loop; threads take turns."""
|
|
214
233
|
with self._loop_lock:
|
|
215
234
|
return self._loop.run_until_complete(work)
|
|
216
235
|
|