precis-cli 0.1.3__py3-none-any.whl
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.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/migrate.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI AI 配置迁移命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 `ai migrate` 子命令,从旧脚本迁移生成 Precis V2 配置
|
|
22
|
+
- 支持 Python pandas / SQL / Excel 公式 / 自然语言
|
|
23
|
+
- 默认仅输出 YAML 预览,加 --apply 后写盘
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- AIMigrateCommand 继承 Command 基类
|
|
27
|
+
- 直接调用 ConfigMigrationService,不依赖后端 HTTP
|
|
28
|
+
- 写盘逻辑委托 shared_services.generation_ops.apply_generated_config(CLI/TUI 同源)
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
precis> ai migrate scripts/legacy_rules.sql data/users.xlsx
|
|
32
|
+
precis> ai migrate rules.py data/*.xlsx --apply
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
YAML 预览或写盘成功提示
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import asyncio
|
|
41
|
+
import logging
|
|
42
|
+
import os
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Any
|
|
45
|
+
|
|
46
|
+
from app.cli.shared_services.generation_ops import (
|
|
47
|
+
SUPPORTED_EXTENSIONS,
|
|
48
|
+
)
|
|
49
|
+
from app.cli.shared_services.generation_ops import (
|
|
50
|
+
apply_generated_config as _apply_generated_config,
|
|
51
|
+
)
|
|
52
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
53
|
+
from app.cli.shell.formatter import Formatter
|
|
54
|
+
from app.shared.services.ai.migrate_service import ConfigMigrationService
|
|
55
|
+
from app.shared.services.llm.generation import (
|
|
56
|
+
GenerationOptions,
|
|
57
|
+
ProfilingOptions,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
logger = logging.getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _infer_language(file_path: str) -> str:
|
|
64
|
+
"""根据文件扩展名推断脚本语言。"""
|
|
65
|
+
lower = file_path.lower()
|
|
66
|
+
if lower.endswith(".py"):
|
|
67
|
+
return "python"
|
|
68
|
+
if lower.endswith(".sql"):
|
|
69
|
+
return "sql"
|
|
70
|
+
if lower.endswith(".xlsx") or lower.endswith(".xls") or lower.endswith(".csv"):
|
|
71
|
+
return "excel_formula"
|
|
72
|
+
return "natural_language"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AIMigrateCommand(Command):
|
|
76
|
+
"""AI 配置迁移命令。
|
|
77
|
+
|
|
78
|
+
从旧脚本或业务描述迁移生成 Schema、Constraint、Regex 配置。
|
|
79
|
+
默认输出 YAML 预览,使用 --apply 写入项目目录。
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
super().__init__("migrate", aliases=["mig"])
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def description(self) -> str:
|
|
87
|
+
return "从旧脚本迁移生成 Precis V2 配置"
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def usage(self) -> str:
|
|
91
|
+
return "ai migrate <script_file> [data_files...] [--apply] [--language python|sql|excel_formula|natural_language] [--max-iterations N]"
|
|
92
|
+
|
|
93
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
94
|
+
"""执行配置迁移命令。
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
args: 命令参数列表
|
|
98
|
+
context: 项目上下文
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
命令执行结果
|
|
102
|
+
"""
|
|
103
|
+
if not context.is_project_open:
|
|
104
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
105
|
+
|
|
106
|
+
project_path = context.project_path
|
|
107
|
+
if not project_path:
|
|
108
|
+
return CommandResult.error("项目路径为空")
|
|
109
|
+
|
|
110
|
+
if not args:
|
|
111
|
+
return CommandResult.error(f"请提供脚本文件路径\n用法: {self.usage}")
|
|
112
|
+
|
|
113
|
+
# 解析参数
|
|
114
|
+
script_path = args[0]
|
|
115
|
+
file_patterns: list[str] = []
|
|
116
|
+
apply = False
|
|
117
|
+
language: str | None = None
|
|
118
|
+
max_iterations = 2
|
|
119
|
+
sample_rows = 100
|
|
120
|
+
sample_values = 100
|
|
121
|
+
|
|
122
|
+
i = 1
|
|
123
|
+
while i < len(args):
|
|
124
|
+
arg = args[i]
|
|
125
|
+
if arg == "--apply":
|
|
126
|
+
apply = True
|
|
127
|
+
elif arg == "--language":
|
|
128
|
+
i += 1
|
|
129
|
+
if i < len(args):
|
|
130
|
+
language = args[i]
|
|
131
|
+
else:
|
|
132
|
+
return CommandResult.error("--language 需要参数")
|
|
133
|
+
elif arg == "--max-iterations":
|
|
134
|
+
i += 1
|
|
135
|
+
if i < len(args):
|
|
136
|
+
try:
|
|
137
|
+
max_iterations = max(1, min(5, int(args[i])))
|
|
138
|
+
except ValueError:
|
|
139
|
+
return CommandResult.error(f"--max-iterations 需要整数, got: {args[i]}")
|
|
140
|
+
else:
|
|
141
|
+
return CommandResult.error("--max-iterations 需要参数")
|
|
142
|
+
elif arg == "--sample-rows":
|
|
143
|
+
i += 1
|
|
144
|
+
if i < len(args):
|
|
145
|
+
try:
|
|
146
|
+
sample_rows = max(1, int(args[i]))
|
|
147
|
+
except ValueError:
|
|
148
|
+
return CommandResult.error(f"--sample-rows 需要整数, got: {args[i]}")
|
|
149
|
+
else:
|
|
150
|
+
return CommandResult.error("--sample-rows 需要参数")
|
|
151
|
+
elif arg == "--sample-values":
|
|
152
|
+
i += 1
|
|
153
|
+
if i < len(args):
|
|
154
|
+
try:
|
|
155
|
+
sample_values = max(1, int(args[i]))
|
|
156
|
+
except ValueError:
|
|
157
|
+
return CommandResult.error(f"--sample-values 需要整数, got: {args[i]}")
|
|
158
|
+
else:
|
|
159
|
+
return CommandResult.error("--sample-values 需要参数")
|
|
160
|
+
elif arg.startswith("--"):
|
|
161
|
+
return CommandResult.error(f"未知参数: {arg}\n用法: {self.usage}")
|
|
162
|
+
else:
|
|
163
|
+
file_patterns.append(arg)
|
|
164
|
+
i += 1
|
|
165
|
+
|
|
166
|
+
# 解析脚本路径
|
|
167
|
+
if not os.path.isabs(script_path):
|
|
168
|
+
script_path = os.path.join(project_path, script_path)
|
|
169
|
+
if not os.path.exists(script_path):
|
|
170
|
+
return CommandResult.error(f"脚本文件不存在: {script_path}")
|
|
171
|
+
|
|
172
|
+
if language is None:
|
|
173
|
+
language = _infer_language(script_path)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
with open(script_path, encoding="utf-8") as f:
|
|
177
|
+
script_content = f.read()
|
|
178
|
+
except Exception as e:
|
|
179
|
+
return CommandResult.error(f"读取脚本文件失败: {e}")
|
|
180
|
+
|
|
181
|
+
# 展开数据文件路径
|
|
182
|
+
file_paths: list[str] = []
|
|
183
|
+
for pattern in file_patterns:
|
|
184
|
+
if not os.path.isabs(pattern):
|
|
185
|
+
pattern = os.path.join(project_path, pattern)
|
|
186
|
+
import glob as _glob
|
|
187
|
+
|
|
188
|
+
matched = _glob.glob(pattern)
|
|
189
|
+
if matched:
|
|
190
|
+
file_paths.extend(matched)
|
|
191
|
+
elif os.path.exists(pattern):
|
|
192
|
+
file_paths.append(pattern)
|
|
193
|
+
|
|
194
|
+
file_paths = [p for p in file_paths if p.lower().endswith(SUPPORTED_EXTENSIONS)]
|
|
195
|
+
if not file_paths:
|
|
196
|
+
return CommandResult.error("请提供至少一个数据文件(.xlsx/.xls/.csv/.json/.jsonl)")
|
|
197
|
+
|
|
198
|
+
# 获取项目信息
|
|
199
|
+
config = context.project_config or {}
|
|
200
|
+
project_name = config.get("project", {}).get("name", Path(project_path).name)
|
|
201
|
+
project_id = config.get("project", {}).get("id", project_name)
|
|
202
|
+
|
|
203
|
+
print(Formatter.header("\nAI 配置迁移"))
|
|
204
|
+
print(Formatter.info(f"项目: {project_name}"))
|
|
205
|
+
print(Formatter.info(f"脚本: {os.path.relpath(script_path, project_path)} ({language})"))
|
|
206
|
+
print(Formatter.info("数据文件:"))
|
|
207
|
+
for p in file_paths:
|
|
208
|
+
print(f" - {os.path.relpath(p, project_path)}")
|
|
209
|
+
|
|
210
|
+
def progress_callback(stage: str, progress: float, extra: dict[str, Any] | None = None) -> None:
|
|
211
|
+
"""终端进度回调。"""
|
|
212
|
+
msg = extra.get("message") if extra else None
|
|
213
|
+
prefix = f"[{stage}]" if not msg else f"[{stage}] {msg}"
|
|
214
|
+
print(Formatter.info(f" {prefix} {progress:.0f}%"))
|
|
215
|
+
|
|
216
|
+
service = ConfigMigrationService()
|
|
217
|
+
profiling_opts = ProfilingOptions(
|
|
218
|
+
sample_rows=sample_rows,
|
|
219
|
+
sample_values_per_column=sample_values,
|
|
220
|
+
)
|
|
221
|
+
gen_opts = GenerationOptions(
|
|
222
|
+
generate_schemas=True,
|
|
223
|
+
generate_constraints=True,
|
|
224
|
+
generate_regex_nodes=False,
|
|
225
|
+
keep_existing=True,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# CLI 是同步环境:asyncio.run 创建一次性事件循环并在结束时确保关闭
|
|
229
|
+
# (不再 get_running_loop/new_event_loop 泄漏循环)
|
|
230
|
+
try:
|
|
231
|
+
result = asyncio.run(
|
|
232
|
+
service.migrate_from_script(
|
|
233
|
+
script_content=script_content,
|
|
234
|
+
language=language,
|
|
235
|
+
file_paths=file_paths,
|
|
236
|
+
project_name=project_name,
|
|
237
|
+
project_id=project_id,
|
|
238
|
+
config_path=project_path,
|
|
239
|
+
profiling_options=profiling_opts,
|
|
240
|
+
generation_options=gen_opts,
|
|
241
|
+
max_iterations=max_iterations,
|
|
242
|
+
progress_callback=progress_callback,
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(f"配置迁移失败: {e}", exc_info=True)
|
|
247
|
+
return CommandResult.error(f"配置迁移失败: {e}")
|
|
248
|
+
|
|
249
|
+
if not result.get("success"):
|
|
250
|
+
error = result.get("error") or "配置迁移失败"
|
|
251
|
+
return CommandResult.error(error)
|
|
252
|
+
|
|
253
|
+
yaml_preview = result.get("yaml_preview", "")
|
|
254
|
+
warnings = result.get("warnings", [])
|
|
255
|
+
|
|
256
|
+
if warnings:
|
|
257
|
+
print(Formatter.warning("\n迁移警告:"))
|
|
258
|
+
for w in warnings:
|
|
259
|
+
print(f" - {w}")
|
|
260
|
+
|
|
261
|
+
if apply:
|
|
262
|
+
try:
|
|
263
|
+
_apply_generated_config(result, project_path)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"写盘失败: {e}", exc_info=True)
|
|
266
|
+
return CommandResult.error(f"配置已生成,但写盘失败: {e}")
|
|
267
|
+
|
|
268
|
+
schema_count = len(result.get("schemas", {}))
|
|
269
|
+
constraint_count = len(result.get("constraints", {}))
|
|
270
|
+
regex_count = len(result.get("regex_nodes", {}))
|
|
271
|
+
return CommandResult.ok(
|
|
272
|
+
f"配置已迁移并写入项目:{schema_count} 个 schema,"
|
|
273
|
+
f"{constraint_count} 个 constraint,{regex_count} 个 regex。"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
print(Formatter.header("\nYAML 预览(未写盘,加 --apply 可应用)"))
|
|
277
|
+
print(yaml_preview)
|
|
278
|
+
return CommandResult.ok("配置迁移完成,以上为 YAML 预览。")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
__all__ = ["AIMigrateCommand"]
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/resolver.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI 动作歧义解析模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 解析 AI 返回动作中的表名歧义(支持模糊匹配)
|
|
22
|
+
- 当表名匹配到多个结果时,弹出交互式菜单让用户选择正确的表
|
|
23
|
+
- 处理 VALIDATE_PROJECT 与约束操作(ADD/UPDATE/DELETE_CONSTRAINT_NODE)中的表名解析
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- resolve_table_name(): 使用 find_matching_schemas 对表名进行模糊匹配
|
|
27
|
+
- resolve_ambiguities(): 遍历 AI 返回的动作列表,解析表名歧义并标记跳过项
|
|
28
|
+
- 静默模式下自动选择第一个匹配项,避免阻塞非交互流程
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
resolve_table_name("users", "/path/to/project")
|
|
32
|
+
resolve_ambiguities(actions, "/path/to/project")
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
"users_table_id" 或 None(未找到)
|
|
36
|
+
True/False(是否还有有效动作需要继续执行)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
from app.cli.shell.formatter import Formatter
|
|
45
|
+
from app.cli.shell.interactive_menu import InteractiveMenu
|
|
46
|
+
from app.shared.services.llm.schema_resolver import find_matching_schemas
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve_table_name(table_name: str, project_path: str, silent: bool = False) -> str | None:
|
|
52
|
+
"""将表名解析为表ID。
|
|
53
|
+
|
|
54
|
+
支持模糊匹配,如果有多个匹配则让用户选择。
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
table_name: 用户输入的表名
|
|
58
|
+
project_path: 项目路径
|
|
59
|
+
silent: 是否静默模式(不显示选择菜单)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
表ID,如果未找到则返回 None
|
|
63
|
+
"""
|
|
64
|
+
matches = find_matching_schemas(project_path, table_name)
|
|
65
|
+
|
|
66
|
+
if not matches:
|
|
67
|
+
if not silent:
|
|
68
|
+
print(Formatter.warning(f"[!] 未找到匹配的表: '{table_name}'"))
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
if len(matches) == 1:
|
|
72
|
+
# 只有一个匹配,直接返回
|
|
73
|
+
selected = matches[0]
|
|
74
|
+
if not silent:
|
|
75
|
+
print(Formatter.info(f"[i] 表 '{table_name}' 匹配到: {selected['name']} ({selected['id'][:10]}...)"))
|
|
76
|
+
return selected["id"]
|
|
77
|
+
|
|
78
|
+
# 多个匹配,让用户选择
|
|
79
|
+
if not silent:
|
|
80
|
+
print(f"\n{Formatter.warning('[!]')} 表名 '{table_name}' 匹配到多个表:")
|
|
81
|
+
|
|
82
|
+
menu = InteractiveMenu("请选择要校验的表:")
|
|
83
|
+
for m in matches:
|
|
84
|
+
display_id = f"{m['id'][:10]}..." if len(m["id"]) > 15 else m["id"]
|
|
85
|
+
menu.add_item(m["id"], f"{m['name']} ({display_id})", f"ID: {m['id']}")
|
|
86
|
+
|
|
87
|
+
menu.add_item("_cancel_", "取消", "不执行此操作")
|
|
88
|
+
|
|
89
|
+
choice = menu.show()
|
|
90
|
+
if choice and choice != "_cancel_":
|
|
91
|
+
selected = next(m for m in matches if m["id"] == choice)
|
|
92
|
+
return selected["id"]
|
|
93
|
+
# 用户取消,返回 None
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
# 静默模式下有多个匹配,记录警告并返回第一个(最安全的选择)
|
|
97
|
+
logger.warning(f"表名 '{table_name}' 匹配到多个表,静默模式下默认选择第一个: {matches[0]['name']}")
|
|
98
|
+
return matches[0]["id"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve_ambiguities(actions: list[dict[str, Any]], project_path: str) -> bool:
|
|
102
|
+
"""解决动作中的歧义。返回是否继续执行。
|
|
103
|
+
|
|
104
|
+
遍历 AI 返回的动作列表,解析表名和列名的歧义。
|
|
105
|
+
对于模糊匹配的表名,弹出交互式菜单让用户选择。
|
|
106
|
+
标记为跳过的动作会在最后过滤掉。
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
actions: AI 返回的动作列表
|
|
110
|
+
project_path: 项目路径
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
是否还有有效动作需要继续执行
|
|
114
|
+
"""
|
|
115
|
+
if not actions or not project_path:
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
for action in actions:
|
|
119
|
+
action_type = action.get("actionType", "")
|
|
120
|
+
spec = action.get("constraintSpec", {})
|
|
121
|
+
|
|
122
|
+
# 处理 VALIDATE_PROJECT 的表名解析
|
|
123
|
+
if action_type == "VALIDATE_PROJECT":
|
|
124
|
+
table_name = spec.get("tableName") or spec.get("targetNodeId")
|
|
125
|
+
tables = spec.get("tables", [])
|
|
126
|
+
|
|
127
|
+
# 解析单个表名
|
|
128
|
+
if table_name:
|
|
129
|
+
table_id = resolve_table_name(table_name, project_path)
|
|
130
|
+
if table_id:
|
|
131
|
+
spec["targetNodeId"] = table_id
|
|
132
|
+
spec["tableName"] = table_name
|
|
133
|
+
else:
|
|
134
|
+
action["_skip"] = True
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
# 解析表名列表
|
|
138
|
+
elif tables:
|
|
139
|
+
resolved_ids = []
|
|
140
|
+
for name in tables:
|
|
141
|
+
table_id = resolve_table_name(name, project_path, silent=True)
|
|
142
|
+
if table_id:
|
|
143
|
+
resolved_ids.append(table_id)
|
|
144
|
+
if resolved_ids:
|
|
145
|
+
spec["tableIds"] = resolved_ids
|
|
146
|
+
else:
|
|
147
|
+
action["_skip"] = True
|
|
148
|
+
continue
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# Schema/Regex/Transform/Settings 操作不需要表名歧义解析
|
|
152
|
+
non_table_types = {
|
|
153
|
+
"ADD_SCHEMA",
|
|
154
|
+
"UPDATE_SCHEMA",
|
|
155
|
+
"DELETE_SCHEMA",
|
|
156
|
+
"ADD_REGEX",
|
|
157
|
+
"UPDATE_REGEX",
|
|
158
|
+
"DELETE_REGEX",
|
|
159
|
+
"ADD_TRANSFORM",
|
|
160
|
+
"UPDATE_TRANSFORM",
|
|
161
|
+
"DELETE_TRANSFORM",
|
|
162
|
+
"UPDATE_SETTINGS",
|
|
163
|
+
}
|
|
164
|
+
if action_type in non_table_types:
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
# 处理约束操作的表名解析
|
|
168
|
+
table_id = spec.get("targetNodeId")
|
|
169
|
+
table_name = spec.get("tableName")
|
|
170
|
+
|
|
171
|
+
# 尝试匹配
|
|
172
|
+
identifier = table_id or table_name
|
|
173
|
+
if not identifier:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
matches = find_matching_schemas(project_path, identifier)
|
|
177
|
+
|
|
178
|
+
# 如果有多个模糊匹配,且没有精确匹配 ID 的情况
|
|
179
|
+
if len(matches) > 1:
|
|
180
|
+
print(f"\n{Formatter.warning('[!]')} 发现多个匹配的表 '{identifier}':")
|
|
181
|
+
|
|
182
|
+
menu = InteractiveMenu("请选择正确的表:")
|
|
183
|
+
for m in matches:
|
|
184
|
+
# 检查是否为加密 ID 格式,若是则显示一部分,否则显示全名
|
|
185
|
+
display_id = f"{m['id'][:10]}..." if len(m["id"]) > 15 else m["id"]
|
|
186
|
+
menu.add_item(m["id"], f"{m['name']} ({display_id})", f"ID: {m['id']}")
|
|
187
|
+
|
|
188
|
+
menu.add_item("_skip_", "跳过此操作", "不执行此约束更新")
|
|
189
|
+
|
|
190
|
+
choice = menu.show()
|
|
191
|
+
if choice is None or choice == "_skip_":
|
|
192
|
+
action["_skip"] = True
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
# 找到选中的匹配项
|
|
196
|
+
selected = next(m for m in matches if m["id"] == choice)
|
|
197
|
+
|
|
198
|
+
# 更新 Action 中的关键字段
|
|
199
|
+
spec["targetNodeId"] = selected["id"]
|
|
200
|
+
spec["tableName"] = selected["name"]
|
|
201
|
+
|
|
202
|
+
# 同样更新列 (如果是根据名称匹配,可能也存在列名冲突)
|
|
203
|
+
# 这里简化处理,假设列在特定表内唯一
|
|
204
|
+
print(f"{Formatter.success('[OK]')} 已选择表: {selected['name']}")
|
|
205
|
+
elif len(matches) == 1:
|
|
206
|
+
# 只有一个匹配,直接更新为表ID
|
|
207
|
+
selected = matches[0]
|
|
208
|
+
spec["targetNodeId"] = selected["id"]
|
|
209
|
+
spec["tableName"] = selected["name"]
|
|
210
|
+
else:
|
|
211
|
+
# 4.5: 0 匹配跳过并提示——原实现静默继续(拼错的表名让约束作用于
|
|
212
|
+
# 空表集后"静默成功"),与 VALIDATE 分支的 _skip 行为对齐
|
|
213
|
+
print(f"{Formatter.warning('[!]')} 未找到表 '{identifier}',已跳过该约束操作")
|
|
214
|
+
action["_skip"] = True
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# 统计并提示被跳过的动作
|
|
218
|
+
skipped_actions = [a for a in actions if a.get("_skip")]
|
|
219
|
+
if skipped_actions:
|
|
220
|
+
print(Formatter.info(f"\n[i] 已跳过 {len(skipped_actions)} 个操作(用户取消或表名未找到)"))
|
|
221
|
+
|
|
222
|
+
# 过滤掉标记为跳过的动作
|
|
223
|
+
actions[:] = [a for a in actions if not a.get("_skip")]
|
|
224
|
+
return len(actions) > 0
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/status.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI 配置状态命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 ai status 子命令显示当前 AI Provider 配置状态
|
|
22
|
+
- 列出所有已配置 Provider,标记当前默认项与密钥状态
|
|
23
|
+
- 显示 Provider 的模型、端点等信息
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- AIStatusCommand 继承 Command 基类
|
|
27
|
+
- 通过 CLI 配置管理器获取 Provider 列表和默认项
|
|
28
|
+
- 使用 Formatter 进行颜色高亮输出
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
ai status
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
带颜色高亮的 Provider 配置状态文本
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
38
|
+
from app.cli.shell.config_storage import get_cli_config
|
|
39
|
+
from app.cli.shell.formatter import Formatter
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AIStatusCommand(Command):
|
|
43
|
+
"""AI 配置状态命令。
|
|
44
|
+
|
|
45
|
+
显示所有已配置的 AI Provider 及其状态信息。
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
super().__init__("status")
|
|
50
|
+
self._cli_config = get_cli_config()
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def description(self) -> str:
|
|
54
|
+
return "显示 AI 配置状态"
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def usage(self) -> str:
|
|
58
|
+
return "ai status"
|
|
59
|
+
|
|
60
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
61
|
+
"""执行状态查看命令。
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
args: 命令参数列表(此命令不需要参数)
|
|
65
|
+
context: 项目上下文
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
格式化的配置状态文本
|
|
69
|
+
"""
|
|
70
|
+
providers = self._cli_config.list_providers()
|
|
71
|
+
|
|
72
|
+
lines = [Formatter.header("\nCLI AI 配置状态")]
|
|
73
|
+
|
|
74
|
+
if not providers:
|
|
75
|
+
lines.append(Formatter.warning("\n未配置任何 AI Provider"))
|
|
76
|
+
lines.append(Formatter.info("请编辑 ~/.precis/ai_providers.yaml 配置文件"))
|
|
77
|
+
else:
|
|
78
|
+
lines.append(f"\n已配置的 Providers ({len(providers)}):")
|
|
79
|
+
active = self._cli_config.get_active_provider()
|
|
80
|
+
|
|
81
|
+
for p in providers:
|
|
82
|
+
is_active = active and active.id == p.id
|
|
83
|
+
status_icon = "*" if is_active else " "
|
|
84
|
+
has_key = "[有密钥]" if p.api_key else "[无密钥]"
|
|
85
|
+
|
|
86
|
+
lines.append(f"\n [{status_icon}] {p.name} ({p.id})")
|
|
87
|
+
lines.append(f" 模型: {p.model}")
|
|
88
|
+
lines.append(f" API Key: {has_key}")
|
|
89
|
+
if p.base_url:
|
|
90
|
+
lines.append(f" 端点: {p.base_url}")
|
|
91
|
+
|
|
92
|
+
if active:
|
|
93
|
+
lines.append("\n" + Formatter.success("[*] 当前默认: " + active.name))
|
|
94
|
+
else:
|
|
95
|
+
lines.append("\n" + Formatter.warning("[!] 没有可用的默认 Provider"))
|
|
96
|
+
|
|
97
|
+
lines.append(Formatter.info("\n配置文件: ~/.precis/ai_providers.yaml"))
|
|
98
|
+
lines.append(Formatter.info("API Key 设置方式: 环境变量"))
|
|
99
|
+
lines.append(Formatter.info(" 例如: OPENAI_API_KEY=sk-xxx"))
|
|
100
|
+
|
|
101
|
+
return CommandResult.ok("\n".join(lines))
|