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,401 @@
|
|
|
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
|
+
"""
|
|
17
|
+
@fileoverview 约束动作验证器
|
|
18
|
+
|
|
19
|
+
验证 AI 生成约束相关动作(ADD/UPDATE/DELETE_CONSTRAINT_NODE、VALIDATE_PROJECT)
|
|
20
|
+
的表/字段/约束类型/参数完整性。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from app.shared.services.llm.actions.validation_types import ValidationError
|
|
29
|
+
from app.shared.services.llm.suggestion_utils import (
|
|
30
|
+
normalize_constraint_type,
|
|
31
|
+
suggest_constraints_for_type,
|
|
32
|
+
suggest_similar_column,
|
|
33
|
+
suggest_similar_constraint_type,
|
|
34
|
+
suggest_similar_table,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_constraint_action(
|
|
39
|
+
action: dict[str, Any],
|
|
40
|
+
schema: dict[str, Any],
|
|
41
|
+
index: int,
|
|
42
|
+
required_params: dict[str, list[str]],
|
|
43
|
+
type_compatibility_check: Callable[..., list[ValidationError]],
|
|
44
|
+
foreign_key_check: Callable[..., list[ValidationError]],
|
|
45
|
+
) -> list[ValidationError]:
|
|
46
|
+
"""验证约束操作
|
|
47
|
+
|
|
48
|
+
检查约束动作中的表、字段、约束类型和参数是否合法。
|
|
49
|
+
"""
|
|
50
|
+
errors = []
|
|
51
|
+
action_type = action.get("actionType", "")
|
|
52
|
+
spec = action.get("constraintSpec", {})
|
|
53
|
+
|
|
54
|
+
# 1. 验证表存在
|
|
55
|
+
table_id = spec.get("targetNodeId")
|
|
56
|
+
table_name = spec.get("tableName")
|
|
57
|
+
|
|
58
|
+
if not table_id and not table_name:
|
|
59
|
+
errors.append(
|
|
60
|
+
ValidationError(
|
|
61
|
+
action_index=index,
|
|
62
|
+
action_type=action_type,
|
|
63
|
+
error_type="missing_table",
|
|
64
|
+
message="动作缺少表信息(targetNodeId 或 tableName)",
|
|
65
|
+
suggestion="请指定要操作的表",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
return errors
|
|
69
|
+
|
|
70
|
+
# 检查表是否存在
|
|
71
|
+
table_info = None
|
|
72
|
+
if table_id and table_id in schema["tables"]:
|
|
73
|
+
table_info = schema["tables"][table_id]
|
|
74
|
+
elif table_name:
|
|
75
|
+
table_ids = schema["table_name_to_id"].get(table_name, [])
|
|
76
|
+
if len(table_ids) == 1:
|
|
77
|
+
table_info = schema["tables"].get(table_ids[0])
|
|
78
|
+
elif len(table_ids) > 1:
|
|
79
|
+
errors.append(
|
|
80
|
+
ValidationError(
|
|
81
|
+
action_index=index,
|
|
82
|
+
action_type=action_type,
|
|
83
|
+
error_type="ambiguous_table_name",
|
|
84
|
+
message=f"表名 '{table_name}' 匹配到多个表",
|
|
85
|
+
suggestion=f"请使用表ID,可匹配的表: {', '.join(table_ids[:3])}...",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
return errors
|
|
89
|
+
|
|
90
|
+
if not table_info:
|
|
91
|
+
suggestion = suggest_similar_table(table_name or table_id, schema)
|
|
92
|
+
errors.append(
|
|
93
|
+
ValidationError(
|
|
94
|
+
action_index=index,
|
|
95
|
+
action_type=action_type,
|
|
96
|
+
error_type="table_not_found",
|
|
97
|
+
message=f"表 '{table_name or table_id}' 不存在",
|
|
98
|
+
suggestion=suggestion,
|
|
99
|
+
auto_fixable=False,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
return errors
|
|
103
|
+
|
|
104
|
+
# 2. 验证字段存在
|
|
105
|
+
column_name = spec.get("targetColumn")
|
|
106
|
+
if not column_name:
|
|
107
|
+
errors.append(
|
|
108
|
+
ValidationError(
|
|
109
|
+
action_index=index,
|
|
110
|
+
action_type=action_type,
|
|
111
|
+
error_type="missing_column",
|
|
112
|
+
message="动作缺少字段信息(targetColumn)",
|
|
113
|
+
suggestion="请指定要操作的字段",
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
return errors
|
|
117
|
+
|
|
118
|
+
column_info = None
|
|
119
|
+
for col_id, col_data in table_info["columns"].items():
|
|
120
|
+
if col_id == column_name or col_data["name"] == column_name:
|
|
121
|
+
column_info = col_data
|
|
122
|
+
column_info["id"] = col_id
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if not column_info:
|
|
126
|
+
suggestion = suggest_similar_column(column_name, table_info)
|
|
127
|
+
errors.append(
|
|
128
|
+
ValidationError(
|
|
129
|
+
action_index=index,
|
|
130
|
+
action_type=action_type,
|
|
131
|
+
error_type="column_not_found",
|
|
132
|
+
message=f"字段 '{column_name}' 在表 '{table_info['name']}' 中不存在",
|
|
133
|
+
suggestion=suggestion,
|
|
134
|
+
auto_fixable=False,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return errors
|
|
138
|
+
|
|
139
|
+
# 3. 验证约束类型(ADD 和 UPDATE 需要)
|
|
140
|
+
constraint_type = spec.get("type")
|
|
141
|
+
if action_type in ["ADD_CONSTRAINT_NODE", "UPDATE_CONSTRAINT_NODE"]:
|
|
142
|
+
if not constraint_type:
|
|
143
|
+
errors.append(
|
|
144
|
+
ValidationError(
|
|
145
|
+
action_index=index,
|
|
146
|
+
action_type=action_type,
|
|
147
|
+
error_type="missing_constraint_type",
|
|
148
|
+
message="动作缺少约束类型(type)",
|
|
149
|
+
suggestion="请指定约束类型,如: NotNull, Unique, Range 等",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
return errors
|
|
153
|
+
|
|
154
|
+
normalized_type = normalize_constraint_type(constraint_type)
|
|
155
|
+
if normalized_type not in {
|
|
156
|
+
"NotNull",
|
|
157
|
+
"Unique",
|
|
158
|
+
"Range",
|
|
159
|
+
"AllowedValues",
|
|
160
|
+
"ForeignKey",
|
|
161
|
+
"Conditional",
|
|
162
|
+
"Scripted",
|
|
163
|
+
"DateLogic",
|
|
164
|
+
"Charset",
|
|
165
|
+
"Composite",
|
|
166
|
+
"NOT_NULL",
|
|
167
|
+
"UNIQUE",
|
|
168
|
+
"RANGE",
|
|
169
|
+
"ALLOWED_VALUES",
|
|
170
|
+
"FOREIGN_KEY",
|
|
171
|
+
"CONDITIONAL",
|
|
172
|
+
"DATE_LOGIC",
|
|
173
|
+
"REGEX",
|
|
174
|
+
}:
|
|
175
|
+
suggestion = suggest_similar_constraint_type(constraint_type)
|
|
176
|
+
errors.append(
|
|
177
|
+
ValidationError(
|
|
178
|
+
action_index=index,
|
|
179
|
+
action_type=action_type,
|
|
180
|
+
error_type="invalid_constraint_type",
|
|
181
|
+
message=f"不支持的约束类型: '{constraint_type}'",
|
|
182
|
+
suggestion=suggestion,
|
|
183
|
+
auto_fixable=False,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
return errors
|
|
187
|
+
|
|
188
|
+
# 4. 验证约束参数完整性
|
|
189
|
+
param_errors = validate_constraint_params(
|
|
190
|
+
normalized_type, spec, index, action_type, required_params, foreign_key_check
|
|
191
|
+
)
|
|
192
|
+
errors.extend(param_errors)
|
|
193
|
+
|
|
194
|
+
# 5. 验证字段类型与约束类型兼容性
|
|
195
|
+
if column_info and not param_errors:
|
|
196
|
+
compat_errors = type_compatibility_check(column_info, normalized_type, index, action_type)
|
|
197
|
+
errors.extend(compat_errors)
|
|
198
|
+
|
|
199
|
+
return errors
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def validate_validate_action(action: dict[str, Any], schema: dict[str, Any], index: int) -> list[ValidationError]:
|
|
203
|
+
"""验证 VALIDATE_PROJECT 动作
|
|
204
|
+
|
|
205
|
+
检查动作中指定的表是否存在。
|
|
206
|
+
"""
|
|
207
|
+
errors: list[ValidationError] = []
|
|
208
|
+
action_type = action.get("actionType", "")
|
|
209
|
+
spec = action.get("constraintSpec", {})
|
|
210
|
+
|
|
211
|
+
table_id = spec.get("targetNodeId")
|
|
212
|
+
table_name = spec.get("tableName")
|
|
213
|
+
tables = spec.get("tables") or spec.get("tableIds", [])
|
|
214
|
+
|
|
215
|
+
if not table_id and not table_name and not tables:
|
|
216
|
+
return errors
|
|
217
|
+
|
|
218
|
+
tables_to_check = []
|
|
219
|
+
if tables:
|
|
220
|
+
tables_to_check = tables
|
|
221
|
+
elif table_id:
|
|
222
|
+
tables_to_check = [table_id]
|
|
223
|
+
elif table_name:
|
|
224
|
+
tables_to_check = [table_name]
|
|
225
|
+
|
|
226
|
+
for table_ref in tables_to_check:
|
|
227
|
+
table_exists = False
|
|
228
|
+
if table_ref in schema["tables"]:
|
|
229
|
+
table_exists = True
|
|
230
|
+
elif table_ref in schema["table_name_to_id"]:
|
|
231
|
+
table_exists = True
|
|
232
|
+
|
|
233
|
+
if not table_exists:
|
|
234
|
+
suggestion = suggest_similar_table(table_ref, schema)
|
|
235
|
+
errors.append(
|
|
236
|
+
ValidationError(
|
|
237
|
+
action_index=index,
|
|
238
|
+
action_type=action_type,
|
|
239
|
+
error_type="table_not_found",
|
|
240
|
+
message=f"要校验的表 '{table_ref}' 不存在",
|
|
241
|
+
suggestion=suggestion,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return errors
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def validate_constraint_params(
|
|
249
|
+
constraint_type: str,
|
|
250
|
+
spec: dict[str, Any],
|
|
251
|
+
index: int,
|
|
252
|
+
action_type: str,
|
|
253
|
+
required_params: dict[str, list[str]],
|
|
254
|
+
foreign_key_check: Callable[..., list[ValidationError]],
|
|
255
|
+
) -> list[ValidationError]:
|
|
256
|
+
"""验证约束参数完整性
|
|
257
|
+
|
|
258
|
+
检查约束动作中是否包含该约束类型所需的必填参数。
|
|
259
|
+
"""
|
|
260
|
+
errors = []
|
|
261
|
+
params = spec.get("params", {})
|
|
262
|
+
|
|
263
|
+
# DateLogic range 模式参数完整性检查(不依赖 required_params)
|
|
264
|
+
if constraint_type == "DateLogic":
|
|
265
|
+
logic_mode = params.get("logicMode", "compare")
|
|
266
|
+
compare_op = params.get("compareOp", "gt")
|
|
267
|
+
if logic_mode == "compare" and compare_op == "range":
|
|
268
|
+
has_date_start = "referenceDate" in params and params["referenceDate"] is not None
|
|
269
|
+
has_date_end = "referenceDateEnd" in params and params["referenceDateEnd"] is not None
|
|
270
|
+
has_col_start = "referenceColumn" in params and params["referenceColumn"] is not None
|
|
271
|
+
has_col_end = "referenceColumnEnd" in params and params["referenceColumnEnd"] is not None
|
|
272
|
+
if not ((has_date_start and has_date_end) or (has_col_start and has_col_end)):
|
|
273
|
+
errors.append(
|
|
274
|
+
ValidationError(
|
|
275
|
+
action_index=index,
|
|
276
|
+
action_type=action_type,
|
|
277
|
+
error_type="missing_required_param",
|
|
278
|
+
message="DateLogic range 模式需要同时提供起点和终点(referenceDate + referenceDateEnd,或 referenceColumn + referenceColumnEnd)",
|
|
279
|
+
suggestion="请添加 params.referenceDateEnd / params.referenceColumnEnd",
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
required = required_params.get(constraint_type, [])
|
|
284
|
+
if required:
|
|
285
|
+
if constraint_type == "Range":
|
|
286
|
+
has_min = "min" in params and params["min"] is not None
|
|
287
|
+
has_max = "max" in params and params["max"] is not None
|
|
288
|
+
if not has_min and not has_max:
|
|
289
|
+
errors.append(
|
|
290
|
+
ValidationError(
|
|
291
|
+
action_index=index,
|
|
292
|
+
action_type=action_type,
|
|
293
|
+
error_type="missing_required_param",
|
|
294
|
+
message=f"{constraint_type} 约束需要至少一个 'min' 或 'max' 参数",
|
|
295
|
+
suggestion="请添加 params.min 或 params.max",
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
missing = [p for p in required if p not in params or params[p] is None]
|
|
300
|
+
if missing:
|
|
301
|
+
errors.append(
|
|
302
|
+
ValidationError(
|
|
303
|
+
action_index=index,
|
|
304
|
+
action_type=action_type,
|
|
305
|
+
error_type="missing_required_param",
|
|
306
|
+
message=f"{constraint_type} 约束缺少必需参数: {', '.join(missing)}",
|
|
307
|
+
suggestion=f"请在 params 中添加: {', '.join(missing)}",
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if constraint_type == "ForeignKey":
|
|
312
|
+
fk_errors = foreign_key_check(spec, index, action_type)
|
|
313
|
+
errors.extend(fk_errors)
|
|
314
|
+
|
|
315
|
+
return errors
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def validate_foreign_key_reference(
|
|
319
|
+
spec: dict[str, Any],
|
|
320
|
+
index: int,
|
|
321
|
+
action_type: str,
|
|
322
|
+
schema: dict[str, Any],
|
|
323
|
+
) -> list[ValidationError]:
|
|
324
|
+
"""验证外键引用表和字段存在
|
|
325
|
+
|
|
326
|
+
检查外键约束引用的目标表和目标字段是否存在于项目中。
|
|
327
|
+
"""
|
|
328
|
+
errors = []
|
|
329
|
+
params = spec.get("params", {})
|
|
330
|
+
|
|
331
|
+
to_table_id = params.get("toTableId")
|
|
332
|
+
to_column_id = params.get("toColumnId")
|
|
333
|
+
|
|
334
|
+
if to_table_id:
|
|
335
|
+
if to_table_id not in schema["tables"]:
|
|
336
|
+
suggestion = suggest_similar_table(to_table_id, schema)
|
|
337
|
+
errors.append(
|
|
338
|
+
ValidationError(
|
|
339
|
+
action_index=index,
|
|
340
|
+
action_type=action_type,
|
|
341
|
+
error_type="foreign_key_table_not_found",
|
|
342
|
+
message=f"外键引用的表 '{to_table_id}' 不存在",
|
|
343
|
+
suggestion=suggestion,
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
ref_table = schema["tables"][to_table_id]
|
|
348
|
+
if to_column_id:
|
|
349
|
+
column_exists = False
|
|
350
|
+
for col_id, col_data in ref_table["columns"].items():
|
|
351
|
+
if col_id == to_column_id or col_data["name"] == to_column_id:
|
|
352
|
+
column_exists = True
|
|
353
|
+
break
|
|
354
|
+
|
|
355
|
+
if not column_exists:
|
|
356
|
+
available_cols = list(ref_table["columns"].keys())[:5]
|
|
357
|
+
errors.append(
|
|
358
|
+
ValidationError(
|
|
359
|
+
action_index=index,
|
|
360
|
+
action_type=action_type,
|
|
361
|
+
error_type="foreign_key_column_not_found",
|
|
362
|
+
message=f"外键引用的字段 '{to_column_id}' 在表 '{ref_table['name']}' 中不存在",
|
|
363
|
+
suggestion=f"可用字段: {', '.join(available_cols)}...",
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return errors
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def validate_type_compatibility(
|
|
371
|
+
column_info: dict[str, Any],
|
|
372
|
+
constraint_type: str,
|
|
373
|
+
index: int,
|
|
374
|
+
action_type: str,
|
|
375
|
+
) -> list[ValidationError]:
|
|
376
|
+
"""验证字段类型与约束类型兼容性
|
|
377
|
+
|
|
378
|
+
检查约束类型是否适用于目标字段的数据类型。
|
|
379
|
+
"""
|
|
380
|
+
errors = []
|
|
381
|
+
col_type = column_info.get("type", "string").lower()
|
|
382
|
+
|
|
383
|
+
# Range 是数值范围约束,需数值类型。
|
|
384
|
+
# 注:DateLogic 历史上被误列入此集合(且比对 "DATE_LOGIC" 大写形式永不命中 = 死代码),
|
|
385
|
+
# 实际它是日期约束,应允许 date/datetime 类型。这里移出 numeric_constraints,
|
|
386
|
+
# 保持其原有"不做数值类型限制"的实际行为(消除死代码,不改变语义)。
|
|
387
|
+
numeric_constraints = {"Range"}
|
|
388
|
+
numeric_types = {"integer", "int", "decimal", "float", "number", "numeric"}
|
|
389
|
+
|
|
390
|
+
if constraint_type in numeric_constraints and col_type not in numeric_types:
|
|
391
|
+
errors.append(
|
|
392
|
+
ValidationError(
|
|
393
|
+
action_index=index,
|
|
394
|
+
action_type=action_type,
|
|
395
|
+
error_type="type_incompatibility",
|
|
396
|
+
message=f"不能对 {col_type} 类型的字段使用 {constraint_type} 约束",
|
|
397
|
+
suggestion=f"建议对 {col_type} 类型使用: " + suggest_constraints_for_type(col_type),
|
|
398
|
+
)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return errors
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
"""
|
|
17
|
+
@fileoverview Regex 动作验证器
|
|
18
|
+
|
|
19
|
+
验证 AI 生成 Regex 相关动作(ADD_REGEX/UPDATE_REGEX/DELETE_REGEX)
|
|
20
|
+
的名称、模式、匹配模式合法性。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from app.shared.services.llm.actions.validation_types import ValidationError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def validate_regex_action(action: dict[str, Any], index: int) -> list[ValidationError]:
|
|
31
|
+
"""验证 Regex 操作
|
|
32
|
+
|
|
33
|
+
检查 regexSpec 中的名称、模式、匹配模式合法性。
|
|
34
|
+
"""
|
|
35
|
+
errors = []
|
|
36
|
+
action_type = action.get("actionType", "")
|
|
37
|
+
spec = action.get("regexSpec", {})
|
|
38
|
+
|
|
39
|
+
if action_type == "ADD_REGEX":
|
|
40
|
+
name = spec.get("name", "")
|
|
41
|
+
pattern = spec.get("pattern", "")
|
|
42
|
+
if not name:
|
|
43
|
+
errors.append(
|
|
44
|
+
ValidationError(
|
|
45
|
+
action_index=index,
|
|
46
|
+
action_type=action_type,
|
|
47
|
+
error_type="missing_regex_name",
|
|
48
|
+
message="Regex 名称不能为空",
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
if not pattern:
|
|
52
|
+
errors.append(
|
|
53
|
+
ValidationError(
|
|
54
|
+
action_index=index,
|
|
55
|
+
action_type=action_type,
|
|
56
|
+
error_type="missing_regex_pattern",
|
|
57
|
+
message="Regex 模式不能为空",
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
match_mode = spec.get("matchMode")
|
|
62
|
+
if match_mode and match_mode not in {"full", "partial", "extract"}:
|
|
63
|
+
errors.append(
|
|
64
|
+
ValidationError(
|
|
65
|
+
action_index=index,
|
|
66
|
+
action_type=action_type,
|
|
67
|
+
error_type="invalid_match_mode",
|
|
68
|
+
message=f"不支持的匹配模式: '{match_mode}'",
|
|
69
|
+
suggestion="可选: full, partial, extract",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if action_type in ("UPDATE_REGEX", "DELETE_REGEX"):
|
|
74
|
+
regex_id = spec.get("regexId") or spec.get("id") or spec.get("name")
|
|
75
|
+
if not regex_id:
|
|
76
|
+
errors.append(
|
|
77
|
+
ValidationError(
|
|
78
|
+
action_index=index,
|
|
79
|
+
action_type=action_type,
|
|
80
|
+
error_type="missing_regex_id",
|
|
81
|
+
message="更新/删除 Regex 需要指定 regexId 或 name",
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return errors
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
"""
|
|
17
|
+
@fileoverview Schema 动作验证器
|
|
18
|
+
|
|
19
|
+
验证 AI 生成 Schema 相关动作(ADD_SCHEMA/UPDATE_SCHEMA/DELETE_SCHEMA)
|
|
20
|
+
的名称、列定义合法性。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from app.shared.services.llm.actions.registry import DATA_TYPES
|
|
28
|
+
from app.shared.services.llm.actions.validation_types import ValidationError
|
|
29
|
+
|
|
30
|
+
# 数据类型白名单从注册表派生(单一事实源)
|
|
31
|
+
VALID_DATA_TYPES = DATA_TYPES
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validate_schema_action(action: dict[str, Any], index: int) -> list[ValidationError]:
|
|
35
|
+
"""验证 Schema 操作
|
|
36
|
+
|
|
37
|
+
检查 schemaSpec 中的名称、列定义合法性。
|
|
38
|
+
"""
|
|
39
|
+
errors = []
|
|
40
|
+
action_type = action.get("actionType", "")
|
|
41
|
+
spec = action.get("schemaSpec", {})
|
|
42
|
+
|
|
43
|
+
name = spec.get("name", "")
|
|
44
|
+
schema_id = spec.get("schemaId") or spec.get("id")
|
|
45
|
+
|
|
46
|
+
if action_type in ("ADD_SCHEMA", "UPDATE_SCHEMA") and not name and not schema_id:
|
|
47
|
+
errors.append(
|
|
48
|
+
ValidationError(
|
|
49
|
+
action_index=index,
|
|
50
|
+
action_type=action_type,
|
|
51
|
+
error_type="missing_schema_name",
|
|
52
|
+
message="Schema 名称不能为空",
|
|
53
|
+
suggestion="请指定 name 字段",
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
columns = spec.get("columns", [])
|
|
58
|
+
if columns is not None:
|
|
59
|
+
for col in columns:
|
|
60
|
+
col_type = col.get("type", "string")
|
|
61
|
+
if col_type not in VALID_DATA_TYPES:
|
|
62
|
+
errors.append(
|
|
63
|
+
ValidationError(
|
|
64
|
+
action_index=index,
|
|
65
|
+
action_type=action_type,
|
|
66
|
+
error_type="invalid_column_type",
|
|
67
|
+
message=f"不支持的数据类型: '{col_type}'",
|
|
68
|
+
suggestion=f"可用类型: {', '.join(sorted(VALID_DATA_TYPES))}",
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if action_type in ("UPDATE_SCHEMA", "DELETE_SCHEMA") and not schema_id and not name:
|
|
73
|
+
errors.append(
|
|
74
|
+
ValidationError(
|
|
75
|
+
action_index=index,
|
|
76
|
+
action_type=action_type,
|
|
77
|
+
error_type="missing_schema_id",
|
|
78
|
+
message="更新/删除 Schema 需要指定 schemaId 或 name",
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return errors
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
"""
|
|
17
|
+
@fileoverview Settings 动作验证器
|
|
18
|
+
|
|
19
|
+
验证 AI 生成 Settings 操作(UPDATE_SETTINGS)的 category 和 settings 合法性。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from app.shared.services.llm.actions.registry import SETTINGS_CATEGORIES
|
|
27
|
+
from app.shared.services.llm.actions.validation_types import ValidationError
|
|
28
|
+
|
|
29
|
+
# 设置分类白名单从注册表派生(单一事实源)
|
|
30
|
+
VALID_SETTINGS_CATEGORIES = SETTINGS_CATEGORIES
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def validate_settings_action(action: dict[str, Any], index: int) -> list[ValidationError]:
|
|
34
|
+
"""验证 Settings 操作
|
|
35
|
+
|
|
36
|
+
检查 settingsSpec 中的 category 和 settings 合法性。
|
|
37
|
+
"""
|
|
38
|
+
errors = []
|
|
39
|
+
action_type = action.get("actionType", "")
|
|
40
|
+
spec = action.get("settingsSpec", {})
|
|
41
|
+
|
|
42
|
+
category = spec.get("category", "")
|
|
43
|
+
settings = spec.get("settings", {})
|
|
44
|
+
|
|
45
|
+
if not category:
|
|
46
|
+
errors.append(
|
|
47
|
+
ValidationError(
|
|
48
|
+
action_index=index,
|
|
49
|
+
action_type=action_type,
|
|
50
|
+
error_type="missing_settings_category",
|
|
51
|
+
message="缺少 settings category",
|
|
52
|
+
suggestion=f"可选: {', '.join(sorted(VALID_SETTINGS_CATEGORIES))}",
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
elif category not in VALID_SETTINGS_CATEGORIES:
|
|
56
|
+
errors.append(
|
|
57
|
+
ValidationError(
|
|
58
|
+
action_index=index,
|
|
59
|
+
action_type=action_type,
|
|
60
|
+
error_type="invalid_settings_category",
|
|
61
|
+
message=f"未知的 settings category: '{category}'",
|
|
62
|
+
suggestion=f"可选: {', '.join(sorted(VALID_SETTINGS_CATEGORIES))}",
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not settings:
|
|
67
|
+
errors.append(
|
|
68
|
+
ValidationError(
|
|
69
|
+
action_index=index,
|
|
70
|
+
action_type=action_type,
|
|
71
|
+
error_type="empty_settings",
|
|
72
|
+
message="settings 不能为空",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return errors
|