hap-cli 0.8.23__tar.gz → 0.8.24__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.
- {hap_cli-0.8.23 → hap_cli-0.8.24}/PKG-INFO +1 -1
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/record_cmd.py +15 -6
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/record.py +103 -23
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/overview.md +3 -2
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/record.md +180 -14
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/overview.md +2 -2
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/record.md +166 -12
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_guide.py +11 -0
- hap_cli-0.8.24/hap_cli/tests/test_record_write_fixes.py +122 -0
- hap_cli-0.8.24/hap_cli/tests/test_select_add_option.py +78 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_select_rejects_relation_value.py +10 -6
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/SOURCES.txt +2 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/MANIFEST.in +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/README.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/README.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/context.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/index.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_core.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/charts.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/config.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/errors.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/executor.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/fields.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/hap.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/schema.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/step.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/steps.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/store.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/setup.cfg +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/setup.py +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -84,8 +84,12 @@ def record_get(ctx, worksheet_id, row_id, view_id):
|
|
|
84
84
|
"list of option names or keys; member/relation fields a "
|
|
85
85
|
"list of ids). Use either this or -f, not both.")
|
|
86
86
|
@click.option("--no-workflow", is_flag=True, help="Don't trigger workflows")
|
|
87
|
+
@click.option(
|
|
88
|
+
"--app-id", "-a", default="",
|
|
89
|
+
help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
|
|
90
|
+
)
|
|
87
91
|
@pass_context
|
|
88
|
-
def record_create(ctx, worksheet_id, field, fields_json, no_workflow):
|
|
92
|
+
def record_create(ctx, worksheet_id, field, fields_json, no_workflow, app_id):
|
|
89
93
|
"""Create a new record.
|
|
90
94
|
|
|
91
95
|
Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
|
|
@@ -112,10 +116,11 @@ def record_create(ctx, worksheet_id, field, fields_json, no_workflow):
|
|
|
112
116
|
raise click.UsageError(str(exc))
|
|
113
117
|
else:
|
|
114
118
|
raw = _parse_fields(field)
|
|
115
|
-
controls = rec_mod.encode_controls_by_id(
|
|
119
|
+
controls = rec_mod.encode_controls_by_id(
|
|
120
|
+
session, worksheet_id, raw, app_id=app_id)
|
|
116
121
|
data = rec_mod.create_record(
|
|
117
122
|
session, worksheet_id, controls,
|
|
118
|
-
trigger_workflow=not no_workflow,
|
|
123
|
+
trigger_workflow=not no_workflow, app_id=app_id,
|
|
119
124
|
)
|
|
120
125
|
ctx.output(data, lambda d: click.echo(f"Created: {d}"))
|
|
121
126
|
except Exception as e:
|
|
@@ -153,8 +158,12 @@ def _fields_json_to_raw(fields_json):
|
|
|
153
158
|
"list of option names or keys; member/relation fields a "
|
|
154
159
|
"list of ids). Use either this or -f, not both.")
|
|
155
160
|
@click.option("--no-workflow", is_flag=True, help="Don't trigger workflows")
|
|
161
|
+
@click.option(
|
|
162
|
+
"--app-id", "-a", default="",
|
|
163
|
+
help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
|
|
164
|
+
)
|
|
156
165
|
@pass_context
|
|
157
|
-
def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow):
|
|
166
|
+
def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id):
|
|
158
167
|
"""Update a record.
|
|
159
168
|
|
|
160
169
|
Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
|
|
@@ -180,10 +189,10 @@ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow):
|
|
|
180
189
|
else:
|
|
181
190
|
raw = _parse_fields(field)
|
|
182
191
|
controls = rec_mod.encode_controls_by_id(
|
|
183
|
-
session, worksheet_id, raw, for_update=True)
|
|
192
|
+
session, worksheet_id, raw, for_update=True, app_id=app_id)
|
|
184
193
|
data = rec_mod.update_record(
|
|
185
194
|
session, worksheet_id, row_id, controls,
|
|
186
|
-
trigger_workflow=not no_workflow,
|
|
195
|
+
trigger_workflow=not no_workflow, app_id=app_id,
|
|
187
196
|
)
|
|
188
197
|
ctx.output(data, lambda d: click.echo(f"Updated: {d}"))
|
|
189
198
|
except Exception as e:
|
|
@@ -29,12 +29,36 @@ def _option_field_label(ctrl: dict[str, Any]) -> str:
|
|
|
29
29
|
return str(ctrl.get("controlName") or ctrl.get("controlId") or "option field")
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
# The colour pd-openweb stamps on an option created this way
|
|
33
|
+
# (src/components/Form/core/utils.js:224).
|
|
34
|
+
_NEW_OPTION_COLOR = "#1677ff"
|
|
35
|
+
_NEW_OPTION_PREFIX = "add_"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _new_option_payload(text: str) -> str:
|
|
39
|
+
"""Wire form of "create this option and select it".
|
|
40
|
+
|
|
41
|
+
``add_<text>`` is the form's own marker, never a wire value: pd-openweb
|
|
42
|
+
rewrites it in ``formatControlToServer`` (cases 10/11) into a JSON
|
|
43
|
+
string ``{"color", "value"}``, and that is what the server reads as a
|
|
44
|
+
new option. Passing the marker through untranslated is what used to
|
|
45
|
+
store ``{"key": "add_<text>", "value": null}`` — a blank cell, and no
|
|
46
|
+
option added to the field.
|
|
47
|
+
|
|
48
|
+
The frontend rewrites on ``indexOf('add_') > -1``; a prefix test is
|
|
49
|
+
used here so a genuine label containing ``add_`` still resolves to its
|
|
50
|
+
own key.
|
|
51
|
+
"""
|
|
52
|
+
return json.dumps({"color": _NEW_OPTION_COLOR, "value": text},
|
|
53
|
+
ensure_ascii=False)
|
|
54
|
+
|
|
55
|
+
|
|
32
56
|
def _reject_relation_shaped_option(ctrl: dict[str, Any], item: Any) -> None:
|
|
33
57
|
"""Fail loudly when a select cell is handed a relation/member/sub-table
|
|
34
58
|
*object* shape instead of an option value.
|
|
35
59
|
|
|
36
60
|
A single/multi-select cell legitimately carries only an option key, a
|
|
37
|
-
known label, or an ``add_<text>`` new
|
|
61
|
+
known label, or an ``add_<text>`` marker for a new option — never a
|
|
38
62
|
``[{"sid": rowId}]`` / ``{...}`` structure. When such a value slips
|
|
39
63
|
through (e.g. row data maps a relation value to the wrong child field),
|
|
40
64
|
the old code passed it straight to the server, which then failed
|
|
@@ -53,8 +77,9 @@ def _reject_relation_shaped_option(ctrl: dict[str, Any], item: Any) -> None:
|
|
|
53
77
|
if looks_object:
|
|
54
78
|
raise ValueError(
|
|
55
79
|
f"Field '{_option_field_label(ctrl)}' is a select field and "
|
|
56
|
-
f"expects an option key,
|
|
57
|
-
f"
|
|
80
|
+
f"expects an option key, an exact option label, or "
|
|
81
|
+
f"'add_<text>' to create a new option, but got a "
|
|
82
|
+
f"relation/object-shaped value: {item!r}. "
|
|
58
83
|
f"This value belongs to a relation or member field, not this "
|
|
59
84
|
f"select field — check the field mapping."
|
|
60
85
|
)
|
|
@@ -86,8 +111,15 @@ def _resolve_option_keys(ctrl: dict[str, Any], raw: Any) -> list[str]:
|
|
|
86
111
|
by_label[str(opt.get("value", ""))] = key
|
|
87
112
|
out: list[str] = []
|
|
88
113
|
for it in items:
|
|
89
|
-
_reject_relation_shaped_option(ctrl, it)
|
|
90
114
|
s = str(it)
|
|
115
|
+
# A known label wins over the marker, so an option literally named
|
|
116
|
+
# "add_something" still resolves to its own key.
|
|
117
|
+
if (isinstance(it, str) and s not in valid_keys and s not in by_label
|
|
118
|
+
and s.startswith(_NEW_OPTION_PREFIX)
|
|
119
|
+
and len(s) > len(_NEW_OPTION_PREFIX)):
|
|
120
|
+
out.append(_new_option_payload(s[len(_NEW_OPTION_PREFIX):]))
|
|
121
|
+
continue
|
|
122
|
+
_reject_relation_shaped_option(ctrl, it)
|
|
91
123
|
out.append(s if s in valid_keys else by_label.get(s, s))
|
|
92
124
|
return out
|
|
93
125
|
|
|
@@ -387,7 +419,7 @@ def _legacy_cell_value(ctrl: dict[str, Any], value: Any) -> str:
|
|
|
387
419
|
|
|
388
420
|
|
|
389
421
|
def _controls_for_write(
|
|
390
|
-
session: Session, worksheet_id: str,
|
|
422
|
+
session: Session, worksheet_id: str, app_id: str = "",
|
|
391
423
|
) -> list[dict[str, Any]]:
|
|
392
424
|
"""Fetch the worksheet's controls for a write, refusing an empty list.
|
|
393
425
|
|
|
@@ -406,7 +438,8 @@ def _controls_for_write(
|
|
|
406
438
|
always has controls, so an empty list means "we could not read the
|
|
407
439
|
fields", never "this worksheet has none". Abort before writing.
|
|
408
440
|
"""
|
|
409
|
-
controls = ws_mod.get_worksheet_controls(
|
|
441
|
+
controls = ws_mod.get_worksheet_controls(
|
|
442
|
+
session, worksheet_id, app_id=app_id)
|
|
410
443
|
if not controls:
|
|
411
444
|
logger.warning(
|
|
412
445
|
"GetWorksheetControls returned no fields for worksheet %s; "
|
|
@@ -427,6 +460,7 @@ def encode_controls_by_id(
|
|
|
427
460
|
raw_controls: list[dict[str, Any]],
|
|
428
461
|
*,
|
|
429
462
|
for_update: bool = False,
|
|
463
|
+
app_id: str = "",
|
|
430
464
|
) -> list[dict[str, Any]]:
|
|
431
465
|
"""Type-encode ``-f CONTROL_ID=VALUE`` cells for create/update.
|
|
432
466
|
|
|
@@ -457,7 +491,7 @@ def encode_controls_by_id(
|
|
|
457
491
|
(in-place edits keyed by ``rowid``, fresh adds, and deletes — see
|
|
458
492
|
:func:`_sublist_update_value`).
|
|
459
493
|
"""
|
|
460
|
-
controls = _controls_for_write(session, worksheet_id)
|
|
494
|
+
controls = _controls_for_write(session, worksheet_id, app_id)
|
|
461
495
|
by_id: dict[str, dict[str, Any]] = {
|
|
462
496
|
c.get("controlId"): c for c in controls if c.get("controlId")
|
|
463
497
|
}
|
|
@@ -860,32 +894,68 @@ def _reject_workflow_suppression(trigger_workflow: bool) -> None:
|
|
|
860
894
|
)
|
|
861
895
|
|
|
862
896
|
|
|
897
|
+
def _raise_on_failed_write(result: Any, action: str) -> Any:
|
|
898
|
+
"""Turn a rejected write into an error instead of a printed success.
|
|
899
|
+
|
|
900
|
+
``AddWorksheetRow`` / ``UpdateWorksheetRow`` answer HTTP 200 with a
|
|
901
|
+
``resultCode`` that says whether the row was actually saved: 1 means
|
|
902
|
+
saved, anything else means it was not. Nothing used to look at it, so
|
|
903
|
+
a validation failure — most often a missing required field, which
|
|
904
|
+
comes back as ``{"resultCode": 31, "badData": [<control ids>]}`` and
|
|
905
|
+
stores nothing — was printed as ``Created: {...}`` and exited 0.
|
|
906
|
+
|
|
907
|
+
Responses with no ``resultCode`` are left alone: not every endpoint
|
|
908
|
+
carries one, and absence is not failure.
|
|
909
|
+
"""
|
|
910
|
+
if not isinstance(result, dict) or "resultCode" not in result:
|
|
911
|
+
return result
|
|
912
|
+
code = result.get("resultCode")
|
|
913
|
+
if code == 1:
|
|
914
|
+
return result
|
|
915
|
+
bad = result.get("badData")
|
|
916
|
+
detail = (f" It rejected these fields: {', '.join(map(str, bad))}."
|
|
917
|
+
if bad else " Check the record still exists and that each value "
|
|
918
|
+
"matches its field type.")
|
|
919
|
+
raise APIError(
|
|
920
|
+
f"The record was not saved — the server refused the {action} "
|
|
921
|
+
f"(resultCode {code}).{detail}",
|
|
922
|
+
code=code if isinstance(code, int) else None,
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
|
|
863
926
|
def create_record(
|
|
864
927
|
session: Session,
|
|
865
928
|
worksheet_id: str,
|
|
866
929
|
controls: list[dict[str, Any]],
|
|
867
930
|
trigger_workflow: bool = True,
|
|
931
|
+
app_id: str = "",
|
|
868
932
|
) -> dict[str, Any]:
|
|
869
933
|
"""Create a new record.
|
|
870
934
|
|
|
935
|
+
``worksheet_id`` identifies the worksheet on its own — a write does not
|
|
936
|
+
depend on which app is currently selected (verified against a worksheet
|
|
937
|
+
while an unrelated app was the default). ``app_id`` is optional and is
|
|
938
|
+
forwarded as ``appId``, which this endpoint's schema carries.
|
|
939
|
+
|
|
871
940
|
Args:
|
|
872
941
|
session: Active session
|
|
873
942
|
worksheet_id: Worksheet ID
|
|
874
943
|
controls: List of {controlId, value} dicts
|
|
875
944
|
trigger_workflow: Whether to trigger workflows
|
|
945
|
+
app_id: Optional application ID
|
|
876
946
|
|
|
877
947
|
Returns:
|
|
878
948
|
Created record info
|
|
879
949
|
"""
|
|
880
950
|
_reject_workflow_suppression(trigger_workflow)
|
|
881
|
-
|
|
882
|
-
"
|
|
883
|
-
"
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
951
|
+
data: dict[str, Any] = {
|
|
952
|
+
"worksheetId": worksheet_id,
|
|
953
|
+
"receiveControls": controls,
|
|
954
|
+
}
|
|
955
|
+
if app_id:
|
|
956
|
+
data["appId"] = app_id
|
|
957
|
+
return _raise_on_failed_write(
|
|
958
|
+
session.api_call("Worksheet", "AddWorksheetRow", data), "create")
|
|
889
959
|
|
|
890
960
|
|
|
891
961
|
def update_record(
|
|
@@ -894,28 +964,38 @@ def update_record(
|
|
|
894
964
|
row_id: str,
|
|
895
965
|
controls: list[dict[str, Any]],
|
|
896
966
|
trigger_workflow: bool = True,
|
|
967
|
+
app_id: str = "",
|
|
897
968
|
) -> dict[str, Any]:
|
|
898
969
|
"""Update an existing record.
|
|
899
970
|
|
|
971
|
+
``app_id`` is accepted for a consistent command surface and is used to
|
|
972
|
+
scope the field-metadata lookup, but it is NOT sent: unlike
|
|
973
|
+
AddWorksheetRow, UpdateWorksheetRow's schema has no ``appId``, and
|
|
974
|
+
inventing one would diverge from the frontend.
|
|
975
|
+
|
|
900
976
|
Args:
|
|
901
977
|
session: Active session
|
|
902
978
|
worksheet_id: Worksheet ID
|
|
903
979
|
row_id: Record/row ID
|
|
904
980
|
controls: List of {controlId, value} dicts
|
|
905
981
|
trigger_workflow: Whether to trigger workflows
|
|
982
|
+
app_id: Optional application ID (metadata scope only)
|
|
906
983
|
|
|
907
984
|
Returns:
|
|
908
985
|
Update result
|
|
909
986
|
"""
|
|
910
987
|
_reject_workflow_suppression(trigger_workflow)
|
|
911
|
-
return
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
988
|
+
return _raise_on_failed_write(
|
|
989
|
+
session.api_call(
|
|
990
|
+
"Worksheet",
|
|
991
|
+
"UpdateWorksheetRow",
|
|
992
|
+
{
|
|
993
|
+
"worksheetId": worksheet_id,
|
|
994
|
+
"rowId": row_id,
|
|
995
|
+
"newOldControl": controls,
|
|
996
|
+
},
|
|
997
|
+
),
|
|
998
|
+
"update",
|
|
919
999
|
)
|
|
920
1000
|
|
|
921
1001
|
|
|
@@ -35,8 +35,9 @@ generated live rather than copied. Expand any of them with `hap <command> --help
|
|
|
35
35
|
- `--json` and `--profile` are **global options and go between `hap` and the
|
|
36
36
|
subcommand**: `hap --json chat list` ✅, `hap chat list --json` ❌ ("No such option").
|
|
37
37
|
- To see a worksheet's fields use `hap worksheet fields WS_ID` — not get / structure.
|
|
38
|
-
-
|
|
39
|
-
|
|
38
|
+
- Writing records **does not depend on the selected app** — a `worksheetId` locates the
|
|
39
|
+
worksheet on its own and `-a/--app-id` is optional. A failing write is never "I forgot
|
|
40
|
+
to select the app".
|
|
40
41
|
- Field value shapes differ a lot and **a wrong one still reports success** —
|
|
41
42
|
read `hap guide record` before writing.
|
|
42
43
|
- Before touching workflow nodes run `hap workflow structure PROCESS_ID` and use the
|
|
@@ -34,8 +34,11 @@ hap worksheet record create <WS_ID> --fields-json '[{"id":"<field>","value":...}
|
|
|
34
34
|
They **cannot be mixed**. With `-f`, multiple values (multi-select, several members or
|
|
35
35
|
relations) are comma-separated.
|
|
36
36
|
|
|
37
|
-
> `
|
|
38
|
-
> `
|
|
37
|
+
> **About `-a/--app-id`**: `create` / `update` / `delete` all accept it, but none of
|
|
38
|
+
> them *needs* it — a `worksheetId` identifies the worksheet on its own. Verified by
|
|
39
|
+
> setting an entirely unrelated app as the default: all three writes still succeeded.
|
|
40
|
+
> So a failing write is never "I forgot to select the app". Passing it is harmless: it
|
|
41
|
+
> scopes the field-metadata lookup, and create forwards it as `appId`.
|
|
39
42
|
|
|
40
43
|
### 1.3 Update replaces; only sub-tables merge
|
|
41
44
|
|
|
@@ -72,7 +75,38 @@ The `_id` alongside it (24 hex chars) is a different internal id. **Passing it t
|
|
|
72
75
|
`record get` reports that the row does not exist or is not permitted**, and it can
|
|
73
76
|
never be used in a relation field.
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
### 1.7 "Created" does not mean it worked — check `resultCode`
|
|
79
|
+
|
|
80
|
+
The server uses `resultCode` to say whether the row was actually saved: **only 1 is
|
|
81
|
+
success.**
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Created: {'resultCode': 1, 'data': {...'rowid': '...'}} <- saved; the rowid is in data
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Anything else means nothing was written, and the CLI raises it as an error and exits
|
|
88
|
+
non-zero:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Error: The record was not saved — the server refused the update (resultCode 4).
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
When the reply carries `badData`, it names the field ids that were rejected.
|
|
95
|
+
|
|
96
|
+
> Note: **"required" is a form-level constraint and the API does not necessarily
|
|
97
|
+
> enforce it.** Omitting a `required: true` field still came back as `resultCode: 1`
|
|
98
|
+
> with the row saved. Do not rely on the API to catch a missing required value — look
|
|
99
|
+
> them up yourself:
|
|
100
|
+
>
|
|
101
|
+
> ```bash
|
|
102
|
+
> hap --json worksheet fields <WS_ID> | python3 -c "
|
|
103
|
+
> import json,sys
|
|
104
|
+
> for f in json.load(sys.stdin):
|
|
105
|
+
> if f.get('required'): print(f['id'], f['name'], f['type'])
|
|
106
|
+
> "
|
|
107
|
+
> ```
|
|
108
|
+
|
|
109
|
+
## 2. By field type## 2. By field type
|
|
76
110
|
|
|
77
111
|
### Text and numbers
|
|
78
112
|
|
|
@@ -130,9 +164,26 @@ hap --json worksheet fields <WS_ID> # options[].key on that field
|
|
|
130
164
|
> (`-f "<field>=high,priority"` splits into two invalid values)
|
|
131
165
|
> - the option simply does not exist
|
|
132
166
|
>
|
|
133
|
-
> Keys have none of these problems.
|
|
134
|
-
|
|
135
|
-
|
|
167
|
+
> Keys have none of these problems.
|
|
168
|
+
|
|
169
|
+
**Creating an option as you write**
|
|
170
|
+
|
|
171
|
+
When the option does not exist yet, prefix it with `add_` and the server mints it and
|
|
172
|
+
selects it:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
hap worksheet record create <WS_ID> -f "<select field>=add_Urgent"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Verified: the field went from `[Pending, Done]` to `[Pending, Done, Urgent]`, and the
|
|
179
|
+
record holds the freshly minted key. Multi-selects and dropdowns behave the same, and it
|
|
180
|
+
mixes with existing values: `-f "<field>=Pending,add_Urgent"`.
|
|
181
|
+
|
|
182
|
+
> Only a leading `add_` is treated as the marker — an option genuinely named
|
|
183
|
+
> `add_something` still resolves to its own key rather than creating a duplicate.
|
|
184
|
+
>
|
|
185
|
+
> To change the field definition itself (reorder options, set colours, delete one), use
|
|
186
|
+
> the app-editor skill from `hap guide skills`.
|
|
136
187
|
|
|
137
188
|
### Members, departments, org roles
|
|
138
189
|
|
|
@@ -233,18 +284,127 @@ field's `dataSource`, linked back through the reverse relation field (`sourceFie
|
|
|
233
284
|
|
|
234
285
|
| Type | typeId | Pass |
|
|
235
286
|
| --- | --- | --- |
|
|
236
|
-
| `Attachment` | 14 | a fixed envelope, as a JSON string |
|
|
287
|
+
| `Attachment` | 14 | a fixed envelope, as a **JSON string** |
|
|
288
|
+
|
|
289
|
+
This is the only accepted shape, and it works with both `-f` and `--fields-json`:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{"attachmentData":[],"attachments":[<descriptor from hap upload>, ...],"knowledgeAtts":[]}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**The whole flow (creating a record with attachments)**
|
|
237
296
|
|
|
238
297
|
```bash
|
|
239
|
-
|
|
240
|
-
|
|
298
|
+
hap app select <APP_ID>
|
|
299
|
+
|
|
300
|
+
# 1) Upload. Several files at once is fine; it returns an array of
|
|
301
|
+
# descriptors. Passing --worksheet-id / -a is recommended (see below).
|
|
302
|
+
hap --json upload ./a.txt ./b.pdf --worksheet-id <WS_ID> -a <APP_ID> > up.json
|
|
241
303
|
|
|
242
|
-
# 2)
|
|
243
|
-
|
|
304
|
+
# 2) Put the whole array in attachments, wrap the envelope, serialize it
|
|
305
|
+
python3 -c "
|
|
306
|
+
import json
|
|
307
|
+
env={'attachmentData':[],'attachments':json.load(open('up.json')),'knowledgeAtts':[]}
|
|
308
|
+
print(json.dumps([{'id':'<attachment field id>','value':json.dumps(env,ensure_ascii=False)}],ensure_ascii=False))
|
|
309
|
+
" > cell.json
|
|
310
|
+
|
|
311
|
+
# 3) Write
|
|
312
|
+
hap worksheet record create <WS_ID> --fields-json "$(cat cell.json)"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> **The two detours everyone takes, both answered with an unexplained
|
|
316
|
+
> server error:**
|
|
317
|
+
>
|
|
318
|
+
> - passing the array `hap upload` returned **directly** (no envelope)
|
|
319
|
+
> - passing just the file's `url` string
|
|
320
|
+
>
|
|
321
|
+
> It must be the full `attachmentData` / `attachments` / `knowledgeAtts`
|
|
322
|
+
> envelope, **serialized to a string as a whole**.
|
|
323
|
+
|
|
324
|
+
> **Do not hand-write that envelope in a shell.** Nested quotes plus escaping is
|
|
325
|
+
> almost guaranteed to go wrong. Generate it with Python into a file and pass
|
|
326
|
+
> `--fields-json "$(cat cell.json)"`, as above.
|
|
327
|
+
|
|
328
|
+
**What `--worksheet-id` / `-a` change**
|
|
329
|
+
|
|
330
|
+
They decide where the file lands in the store:
|
|
331
|
+
|
|
332
|
+
| | `filePath` |
|
|
333
|
+
| --- | --- |
|
|
334
|
+
| without | `pic/20260902/`, `doc/20260902/` (a shared directory per file type) |
|
|
335
|
+
| with | `<orgId>/<appId>/<worksheetId>/20260902/` (filed under org / app / worksheet) |
|
|
336
|
+
|
|
337
|
+
**Pass them** — it is what the web UI does and it keeps files attributable. Writes also
|
|
338
|
+
succeed without them (verified for `.txt` and `.png`: written, read back, downloaded
|
|
339
|
+
intact), so they are not the first thing to suspect when a write fails.
|
|
340
|
+
|
|
341
|
+
**The `file_id` changes once the record is saved**
|
|
342
|
+
|
|
343
|
+
The `fileID` you get from the upload is a client-generated temporary id starting with
|
|
344
|
+
`o_`. Once the record is saved the server assigns a GUID, and that is what you read
|
|
345
|
+
back. **This is normal, not a sign the write went wrong.**
|
|
346
|
+
|
|
347
|
+
**Clearing an attachment field** (it does not follow the general rule)
|
|
348
|
+
|
|
349
|
+
| Pass | Result |
|
|
350
|
+
| --- | --- |
|
|
351
|
+
| `""`, or `-f "<field>="` | clears it |
|
|
352
|
+
| the empty envelope `{"attachmentData":[],"attachments":[],"knowledgeAtts":[]}` | clears it |
|
|
353
|
+
| `[]` | rejected: attachment save failed, bad parameter |
|
|
354
|
+
| `null` | same rejection |
|
|
355
|
+
|
|
356
|
+
`[]` and `null` clear other field types. **They do not clear attachments.**
|
|
357
|
+
|
|
358
|
+
**Update replaces, and there is no append switch**
|
|
359
|
+
|
|
360
|
+
New attachments drop whatever was there. The envelope's `attachmentData` **cannot** be
|
|
361
|
+
used to keep the existing ones: feeding it the `record get` read-back, and separately a
|
|
362
|
+
camelCase object carrying the stored `fileID`, both dropped the old file. (The CLI
|
|
363
|
+
passes attachment JSON through untouched, so this is the server's behaviour.)
|
|
364
|
+
|
|
365
|
+
**To append, build the full list yourself — but do not re-upload.** The bytes are
|
|
366
|
+
already in the store and a descriptor is only metadata pointing at them, so it can be
|
|
367
|
+
rebuilt from what you read back:
|
|
368
|
+
|
|
369
|
+
| Key from `record get` | Rebuilds which descriptor field |
|
|
370
|
+
| --- | --- |
|
|
371
|
+
| `file_path` (`https://host/doc/20260902/`) | `serverName` (`https://host/`) + `filePath` (`doc/20260902/`) |
|
|
372
|
+
| `file_name` (`xxxx.txt`) | `fileName` (`xxxx`) + `fileExt` (`.txt`) |
|
|
373
|
+
| `original_file_name` (`att1.txt`) | `originalFileName` (`att1`) |
|
|
374
|
+
| `file_size` | `fileSize` |
|
|
375
|
+
| `filePath` + `file_name` | `key` |
|
|
376
|
+
| — | `fileID`: any fresh client-side id will do |
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
import json, os
|
|
380
|
+
def rebuild(a):
|
|
381
|
+
"""Turn one record-get read-back entry into a writable descriptor."""
|
|
382
|
+
scheme, rest = a["file_path"].split("//", 1)
|
|
383
|
+
host, _, path = rest.partition("/")
|
|
384
|
+
stem, ext = os.path.splitext(a["file_name"])
|
|
385
|
+
orig, _ = os.path.splitext(a["original_file_name"])
|
|
386
|
+
return {"fileID": "o_" + os.urandom(8).hex(), "fileSize": a["file_size"],
|
|
387
|
+
"serverName": f"{scheme}//{host}/", "filePath": path,
|
|
388
|
+
"fileName": stem, "fileExt": ext,
|
|
389
|
+
"originalFileName": orig, "key": path + a["file_name"]}
|
|
390
|
+
|
|
391
|
+
old = [rebuild(a) for a in record_get_result["<attachment field id>"]]
|
|
392
|
+
new = json.load(open("up.json"))
|
|
393
|
+
env = {"attachmentData": [], "attachments": old + new, "knowledgeAtts": []}
|
|
244
394
|
```
|
|
245
395
|
|
|
246
|
-
|
|
247
|
-
|
|
396
|
+
Both the rebuilt and the newly uploaded files download correctly afterwards.
|
|
397
|
+
|
|
398
|
+
**Three read shapes, none of them the write shape**
|
|
399
|
+
|
|
400
|
+
| Read from | Shape | Keys |
|
|
401
|
+
| --- | --- | --- |
|
|
402
|
+
| `record get` | array | snake_case: `file_id` / `file_name` / `file_path` / `file_size` / `original_file_name` / `preview_url` … |
|
|
403
|
+
| `record list` | array | only `fileName` + `downloadUrl` |
|
|
404
|
+
| the return of `record create/update` | **a JSON string** | camelCase: `fileID` / `originalFilename` / `ext` / `filesize` / `viewUrl` / `downloadUrl` |
|
|
405
|
+
|
|
406
|
+
So **you cannot write back what you read**. Either keep the descriptors `hap upload`
|
|
407
|
+
gave you — they stay reusable — or rebuild them per the table above.
|
|
248
408
|
|
|
249
409
|
## 3. Filtering records
|
|
250
410
|
|
|
@@ -278,12 +438,18 @@ and pivots see `hap guide skills`.
|
|
|
278
438
|
Every one of these returns success while storing something wrong, or nothing:
|
|
279
439
|
|
|
280
440
|
1. **Misspelled field identifier** → the value is ignored.
|
|
281
|
-
2. **Display text instead of an option key, not matching exactly** → stored as an
|
|
441
|
+
2. **Display text instead of an option key, not matching exactly** → stored as an
|
|
442
|
+
invalid key, blank in the UI. To add an option deliberately, use the `add_` prefix.
|
|
282
443
|
3. **A place name in a region field** → stored as `{"code":"<name>","path":""}`.
|
|
283
444
|
4. **A number beyond the field's precision** → silently rounded.
|
|
284
445
|
5. **A title instead of a rowid in a relation** → nothing is linked.
|
|
285
446
|
6. **A value on a read-only field** → ignored (except `DynamicLink`, which fails the
|
|
286
447
|
whole write).
|
|
287
448
|
7. **A multi-value update that omits existing values** → they are gone (sub-tables aside).
|
|
449
|
+
8. **Clearing an attachment with `[]` or `null`** → rejected, and the field still holds
|
|
450
|
+
the old files (use `""`).
|
|
451
|
+
9. **Relying on `attachmentData` to append** → the existing attachments are dropped.
|
|
452
|
+
10. **Omitting a required field** → the API may accept it anyway, saving the row with
|
|
453
|
+
that value empty (see 1.7).
|
|
288
454
|
|
|
289
455
|
So: **always read back after writing.** Do not trust the success alone.
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
- `--json` / `--profile` 是**全局选项,必须放在 `hap` 和子命令之间**:
|
|
33
33
|
`hap --json chat list` ✅,`hap chat list --json` ❌(报 "No such option")。
|
|
34
34
|
- 看工作表字段用 `hap worksheet fields WS_ID`,**不是** get / structure。
|
|
35
|
-
- `
|
|
36
|
-
|
|
35
|
+
- 写记录**不依赖默认应用**——`worksheetId` 就能定位工作表,`-a/--app-id` 可传可不传。
|
|
36
|
+
写入失败时别往「是不是没 select 对应用」上想。
|
|
37
37
|
- 写记录的取值格式差别很大且**写错不报错** —— 动手前先看 `hap guide record`。
|
|
38
38
|
- 操作工作流节点前先 `hap workflow structure PROCESS_ID` 看真实结构,别编造节点 ID。
|
|
39
39
|
- 排查问题开日志:`hap config log on`,日志在 `~/.hap-cli/hap-cli.log`(已对凭证脱敏)。
|