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,286 @@
|
|
|
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
|
+
"""Schema 唯一性检查模块。
|
|
17
|
+
|
|
18
|
+
检测两类 blocker 级冲突:
|
|
19
|
+
1. 多个 schema 文件使用同一 ID(SchemaIdDuplicate)
|
|
20
|
+
2. 多个 schema 指向同一数据源(SchemaSourceDuplicate)
|
|
21
|
+
|
|
22
|
+
本模块不依赖 inspector_helpers,独立性最高(内联构造 actions)。
|
|
23
|
+
|
|
24
|
+
依赖:→ inspection_ids + LoadingError
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import TYPE_CHECKING
|
|
32
|
+
|
|
33
|
+
from app.shared.core.project.loader.loader_parts import inspection_ids as ids
|
|
34
|
+
from app.shared.core.project.loader.types import LoadingError
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from app.shared.core.project.manifest.types import ProjectManifest
|
|
38
|
+
from app.shared.core.project.schema.types import TableSchemaFile
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _schema_entity_label(path: str, name: str | None, schema_id: str) -> str:
|
|
42
|
+
"""生成 schema 实体的用户可读显示名:优先文件名 basename,扫描时已解析出表名则附上。
|
|
43
|
+
|
|
44
|
+
表名与 id/basename 完全相同时不重复附加(如 id=name=users 的常见配置),避免噪音。
|
|
45
|
+
"""
|
|
46
|
+
label = Path(path).name if path else ""
|
|
47
|
+
cleaned = (name or "").strip()
|
|
48
|
+
if cleaned and cleaned != schema_id and cleaned != label:
|
|
49
|
+
label = f"{label}({cleaned})" if label else cleaned
|
|
50
|
+
return label
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _schema_involved_entity(schema_id: str, path: str, name: str | None, navigable: bool) -> dict:
|
|
54
|
+
"""构造 context.involved 实体条目(前后端契约,镜像前端 InspectionInvolvedEntity)。
|
|
55
|
+
|
|
56
|
+
唯一性冲突的实体固定标注 role='conflicting'(冲突方)。字段说明:
|
|
57
|
+
- kind: 实体类型(当前仅 schema)
|
|
58
|
+
- id: 实体 id(schema id)
|
|
59
|
+
- path: 相对配置文件路径(未知时为空串,前端据此隐藏"打开文件")
|
|
60
|
+
- label: 用户可读显示名(优先文件名 basename)
|
|
61
|
+
- navigable: 画布节点可否按 id 唯一寻址(id 冲突时为 False,
|
|
62
|
+
前端不渲染"定位到节点",因为同 id 节点无法区分)
|
|
63
|
+
"""
|
|
64
|
+
return {
|
|
65
|
+
"kind": "schema",
|
|
66
|
+
"id": schema_id,
|
|
67
|
+
"path": path,
|
|
68
|
+
"label": _schema_entity_label(path, name, schema_id),
|
|
69
|
+
"navigable": navigable,
|
|
70
|
+
"role": "conflicting",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _report_schema_id_duplicates(
|
|
75
|
+
id_to_refs: dict[str, list[tuple[str, str]]],
|
|
76
|
+
loading_errors: list[LoadingError],
|
|
77
|
+
) -> None:
|
|
78
|
+
"""根据 id → refs 索引上报重复 id 的 blocker 错误。
|
|
79
|
+
|
|
80
|
+
被 inspect_schema_id_orphan_conflict 调用,基于磁盘扫描结果上报。
|
|
81
|
+
ref_keys 为 (相对文件路径, 文件内表名) 元组列表(schemas/xxx.schema.yaml)。
|
|
82
|
+
"""
|
|
83
|
+
for sid, ref_keys in id_to_refs.items():
|
|
84
|
+
count = len(ref_keys)
|
|
85
|
+
if count > 1:
|
|
86
|
+
primary_ref = ref_keys[0][0]
|
|
87
|
+
# 结构化涉事实体清单:列出全部冲突文件(id 冲突,画布节点无法按 id 区分 → 不可导航)
|
|
88
|
+
involved = [_schema_involved_entity(sid, path, name, False) for path, name in ref_keys]
|
|
89
|
+
loading_errors.append(
|
|
90
|
+
LoadingError(
|
|
91
|
+
id=ids.schema_id_duplicate(sid),
|
|
92
|
+
severity="blocker",
|
|
93
|
+
title=f"表 ID「{sid}」重复",
|
|
94
|
+
description=(
|
|
95
|
+
f"表 ID「{sid}」被 {count} 个 schema 文件同时使用。"
|
|
96
|
+
f"约束按 ID 引用表,ID 重复会导致约束引用指向错误的表。"
|
|
97
|
+
f"请确保每个 schema 的 id 字段唯一。"
|
|
98
|
+
),
|
|
99
|
+
fix_hint=f"请打开下方任一冲突文件,把其中一个 schema 的 id 字段改为新的唯一值(当前均为「{sid}」)。",
|
|
100
|
+
error_type="SchemaIdDuplicate",
|
|
101
|
+
# 归属到第一个冲突 schema 文件,前端"按文件"分组可见
|
|
102
|
+
file_path=primary_ref,
|
|
103
|
+
ref_id=sid,
|
|
104
|
+
message="",
|
|
105
|
+
suggestion="修改其中一个 schema 文件的 id 字段,使其与其他 schema 不同",
|
|
106
|
+
context={"involved": involved},
|
|
107
|
+
actions=[
|
|
108
|
+
# 导航到画布中第一个重复 schema 节点,便于用户定位修改
|
|
109
|
+
# (画布节点以 schema id 为节点 id,target 必须是 sid 而非文件路径)
|
|
110
|
+
{
|
|
111
|
+
"type": "navigate",
|
|
112
|
+
"label": "定位到节点",
|
|
113
|
+
"label_key": "inspection.actions.navigateToNode",
|
|
114
|
+
"target": sid,
|
|
115
|
+
},
|
|
116
|
+
# 复制重复 ID,便于排查
|
|
117
|
+
{
|
|
118
|
+
"type": "copy",
|
|
119
|
+
"label": "复制 ID",
|
|
120
|
+
"label_key": "inspection.actions.copyId",
|
|
121
|
+
"text": sid,
|
|
122
|
+
},
|
|
123
|
+
# 允许忽略(此类问题需用户手动决策保留哪个,无法自动修复)
|
|
124
|
+
{
|
|
125
|
+
"type": "dismiss",
|
|
126
|
+
"label": "忽略",
|
|
127
|
+
"label_key": "inspection.actions.dismiss",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
title_key="inspection.issues.schemaIdDuplicate.title",
|
|
131
|
+
description_key="inspection.issues.schemaIdDuplicate.description",
|
|
132
|
+
fix_hint_key="inspection.issues.schemaIdDuplicate.fixHint",
|
|
133
|
+
message_params={"schemaId": sid, "count": count},
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def inspect_schema_id_orphan_conflict(
|
|
139
|
+
config_path: Path,
|
|
140
|
+
manifest: ProjectManifest,
|
|
141
|
+
schema_files: dict[str, TableSchemaFile],
|
|
142
|
+
loading_errors: list[LoadingError],
|
|
143
|
+
) -> None:
|
|
144
|
+
"""检测磁盘上多个 schema 文件使用同一 ID(blocker)。
|
|
145
|
+
|
|
146
|
+
本函数是 schema ID 唯一性检测的唯一入口。直接扫磁盘所有 .schema.yaml 文件,
|
|
147
|
+
按「文件内 id」建索引,同一 id 被 ≥2 个文件使用即报 blocker。
|
|
148
|
+
|
|
149
|
+
旧实现遍历 schema_files dict,但该 dict 按 id 做 key,冲突时后者会被吞掉,
|
|
150
|
+
永远检测不到重复。基于磁盘的检测不依赖 manifest 白名单,因此在两条路径下
|
|
151
|
+
都生效:
|
|
152
|
+
- load_project(运行时校验,schema_files 只含 manifest 白名单文件)
|
|
153
|
+
- get_v2_full_config(前端资源树,schema_files 含 effective_manifest 合并的孤儿)
|
|
154
|
+
|
|
155
|
+
参数:
|
|
156
|
+
config_path: 项目根目录(manifest 所在目录)
|
|
157
|
+
manifest: 项目清单(保留参数,当前实现基于磁盘扫描,不依赖白名单)
|
|
158
|
+
schema_files: 已加载的 schema dict(保留参数,兼容 inspect_config 调用签名)
|
|
159
|
+
loading_errors: 错误收集列表(会被修改)
|
|
160
|
+
"""
|
|
161
|
+
schemas_dir = config_path / "schemas"
|
|
162
|
+
if not schemas_dir.is_dir():
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
from app.shared.core.io.yaml import read_yaml
|
|
166
|
+
|
|
167
|
+
# 扫磁盘所有 .schema.yaml,按「文件内 id」索引到 (相对路径, 文件内表名)
|
|
168
|
+
# 表名一并收集,用于构造 involved 实体的友好显示名
|
|
169
|
+
id_to_refs: dict[str, list[tuple[str, str]]] = {}
|
|
170
|
+
for filename in os.listdir(schemas_dir):
|
|
171
|
+
if not filename.lower().endswith(".schema.yaml"):
|
|
172
|
+
continue
|
|
173
|
+
abs_path = schemas_dir / filename
|
|
174
|
+
try:
|
|
175
|
+
raw = read_yaml(abs_path)
|
|
176
|
+
except Exception:
|
|
177
|
+
continue
|
|
178
|
+
if not isinstance(raw, dict):
|
|
179
|
+
continue
|
|
180
|
+
file_id = raw.get("id")
|
|
181
|
+
if not isinstance(file_id, str) or not file_id.strip():
|
|
182
|
+
continue
|
|
183
|
+
file_name = raw.get("name")
|
|
184
|
+
id_to_refs.setdefault(file_id.strip(), []).append(
|
|
185
|
+
(f"schemas/{filename}", file_name.strip() if isinstance(file_name, str) else "")
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# 同一 id 被 ≥2 个文件使用 → 冲突
|
|
189
|
+
conflict_index = {sid: refs for sid, refs in id_to_refs.items() if len(refs) > 1}
|
|
190
|
+
|
|
191
|
+
if conflict_index:
|
|
192
|
+
_report_schema_id_duplicates(conflict_index, loading_errors)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def inspect_source_uniqueness(
|
|
196
|
+
schema_files: dict[str, TableSchemaFile],
|
|
197
|
+
loading_errors: list[LoadingError],
|
|
198
|
+
schema_paths: dict[str, str] | None = None,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""检测两个 schema 指向同一数据源(blocker)。
|
|
201
|
+
|
|
202
|
+
schema_paths: schema id → 文件相对路径(来自 manifest 引用),
|
|
203
|
+
用于把问题归属到具体配置文件;缺省时留空,由前端兜底展示。
|
|
204
|
+
"""
|
|
205
|
+
from app.shared.core.project.schema.types_parts.schema_id import normalize_source_key
|
|
206
|
+
|
|
207
|
+
resolved_schema_paths = schema_paths or {}
|
|
208
|
+
source_map: dict[tuple[str, str | None], list[str]] = {}
|
|
209
|
+
for sid, sdoc in schema_files.items():
|
|
210
|
+
source = getattr(sdoc, "source", None)
|
|
211
|
+
if source is None:
|
|
212
|
+
continue
|
|
213
|
+
path = getattr(source, "path", None) or ""
|
|
214
|
+
sheet = getattr(source, "sheet", None)
|
|
215
|
+
if not path:
|
|
216
|
+
continue
|
|
217
|
+
key = normalize_source_key(path, sheet)
|
|
218
|
+
source_map.setdefault(key, []).append(sid)
|
|
219
|
+
|
|
220
|
+
for key, sids in source_map.items():
|
|
221
|
+
if len(sids) > 1:
|
|
222
|
+
path_str, sheet_str = key
|
|
223
|
+
source_display = f"{path_str}"
|
|
224
|
+
if sheet_str:
|
|
225
|
+
source_display += f" ({sheet_str})"
|
|
226
|
+
primary_ref = sids[0]
|
|
227
|
+
# 结构化涉事实体清单:列出全部指向同一数据源的 schema(id 各不相同 → 可导航)
|
|
228
|
+
involved = [
|
|
229
|
+
_schema_involved_entity(
|
|
230
|
+
sid,
|
|
231
|
+
resolved_schema_paths.get(sid, ""),
|
|
232
|
+
getattr(schema_files[sid], "name", None),
|
|
233
|
+
True,
|
|
234
|
+
)
|
|
235
|
+
for sid in sids
|
|
236
|
+
]
|
|
237
|
+
loading_errors.append(
|
|
238
|
+
LoadingError(
|
|
239
|
+
id=ids.schema_source_duplicate(path_str, sheet_str),
|
|
240
|
+
severity="blocker",
|
|
241
|
+
title=f"有表指向了同一个数据文件:{source_display}",
|
|
242
|
+
description=(
|
|
243
|
+
f"数据源 '{source_display}' 被 {len(sids)} 个 schema 引用: {', '.join(sids)}。"
|
|
244
|
+
f"每个数据源只能被一个 schema 定义,否则读取会冲突。请删除重复的 schema 或修改其 source.path。"
|
|
245
|
+
),
|
|
246
|
+
fix_hint=f"请保留其中一个 schema(如 {sids[0]}),删除或修改其他的。涉事实体见下方清单。",
|
|
247
|
+
error_type="SchemaSourceDuplicate",
|
|
248
|
+
# 归属到第一个引用该数据源的 schema 文件,前端"按文件"分组可见
|
|
249
|
+
file_path=resolved_schema_paths.get(primary_ref, ""),
|
|
250
|
+
ref_id=primary_ref,
|
|
251
|
+
message="",
|
|
252
|
+
suggestion=f"保留 schema '{sids[0]}',删除或修改: {', '.join(sids[1:])}",
|
|
253
|
+
context={"involved": involved},
|
|
254
|
+
actions=[
|
|
255
|
+
# 导航到第一个重复 schema 节点
|
|
256
|
+
{
|
|
257
|
+
"type": "navigate",
|
|
258
|
+
"label": "定位到节点",
|
|
259
|
+
"label_key": "inspection.actions.navigateToNode",
|
|
260
|
+
"target": primary_ref,
|
|
261
|
+
},
|
|
262
|
+
# 复制数据源路径,便于排查
|
|
263
|
+
{
|
|
264
|
+
"type": "copy",
|
|
265
|
+
"label": "复制数据源",
|
|
266
|
+
"label_key": "inspection.actions.copyFilePath",
|
|
267
|
+
"text": source_display,
|
|
268
|
+
},
|
|
269
|
+
# 允许忽略(需用户手动决策保留哪个 schema)
|
|
270
|
+
{
|
|
271
|
+
"type": "dismiss",
|
|
272
|
+
"label": "忽略",
|
|
273
|
+
"label_key": "inspection.actions.dismiss",
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
title_key="inspection.issues.sourceDuplicate.title",
|
|
277
|
+
description_key="inspection.issues.sourceDuplicate.description",
|
|
278
|
+
fix_hint_key="inspection.issues.sourceDuplicate.fixHint",
|
|
279
|
+
message_params={
|
|
280
|
+
"sourceDisplay": source_display,
|
|
281
|
+
"count": len(sids),
|
|
282
|
+
"schemas": ", ".join(sids),
|
|
283
|
+
"primarySchema": primary_ref,
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
)
|
|
@@ -0,0 +1,298 @@
|
|
|
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
|
+
功能概述:
|
|
20
|
+
- 把加载阶段(main.py)产生的技术性异常,转换为面向普通用户的友好 LoadingError 字段
|
|
21
|
+
- 统一管理 title / description / fix_hint / severity,避免散落在各处硬编码
|
|
22
|
+
- 为每类加载错误提供 i18n key,前端可优先用本地化文案渲染
|
|
23
|
+
|
|
24
|
+
设计原则:
|
|
25
|
+
- 纯函数,返回 dict(可直接解包进 LoadingError 构造参数)
|
|
26
|
+
- 文案以用户视角描述问题("文件找不到了"而非"FileNotFoundError")
|
|
27
|
+
- 原始技术细节保留在 message 字段,前端折叠展示供高级用户排查
|
|
28
|
+
|
|
29
|
+
覆盖的错误类型:
|
|
30
|
+
- 路径校验失败 (PathValidationError)
|
|
31
|
+
- 文件不存在 (NotFound)
|
|
32
|
+
- 文件解析失败 (ParseError) — 含 YAML 语法 / Pydantic 校验两种子类
|
|
33
|
+
- 模板展开失败 (TemplateExpansionError)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
from app.shared.core.project.loader.loader_parts.inspector_helpers import (
|
|
41
|
+
file_entity_label,
|
|
42
|
+
involved_entity,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# 文件类型 → 中文友好名称映射(用于文案插值)
|
|
46
|
+
# key 为小写化的 file_type(main.py 传入 "ManualData" 等首字母大写形式)
|
|
47
|
+
_RESOURCE_LABELS: dict[str, str] = {
|
|
48
|
+
"schema": "数据表定义",
|
|
49
|
+
"constraint": "约束规则",
|
|
50
|
+
"regex": "正则规则",
|
|
51
|
+
"transform": "数据转换",
|
|
52
|
+
"template": "模板",
|
|
53
|
+
"manualdata": "手动数据",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _resource_label(file_type: str) -> str:
|
|
58
|
+
"""获取文件类型的友好中文名称,未知类型回退到原名。"""
|
|
59
|
+
key = (file_type or "").lower()
|
|
60
|
+
return _RESOURCE_LABELS.get(key, file_type or "配置文件")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _resource_kind(file_type: str) -> str:
|
|
64
|
+
"""文件类型 → involved 实体 kind(小写化;ManualData 归一为 manual_data,与 id 检查一致)。"""
|
|
65
|
+
kind = (file_type or "").strip().lower()
|
|
66
|
+
return "manual_data" if kind == "manualdata" else kind
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _file_involved(file_type: str, ref_id: str, file_path: str) -> dict:
|
|
70
|
+
"""加载期错误的涉事实体:出问题的文件本身(role=file,文件无画布节点 → 不可导航)。"""
|
|
71
|
+
return involved_entity(
|
|
72
|
+
kind=_resource_kind(file_type),
|
|
73
|
+
entity_id=ref_id,
|
|
74
|
+
path=file_path,
|
|
75
|
+
label=file_entity_label(file_path),
|
|
76
|
+
navigable=False,
|
|
77
|
+
role="file",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def path_validation_error(file_type: str, ref_id: str, raw_message: str, file_path: str = "") -> dict:
|
|
82
|
+
"""路径校验失败(路径越界/非法字符等)。
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
file_type: 文件类型(schema/constraint/...)
|
|
86
|
+
ref_id: manifest 中的引用 ID
|
|
87
|
+
raw_message: 原始异常字符串(保留供排查)
|
|
88
|
+
file_path: 校验失败的目标文件路径(绝对路径,供 involved 实体展示)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
LoadingError 友好字段 dict(title/description/fix_hint/severity 等)
|
|
92
|
+
"""
|
|
93
|
+
label = _resource_label(file_type)
|
|
94
|
+
return {
|
|
95
|
+
"severity": "blocker",
|
|
96
|
+
"title": f"{label}的路径存在问题",
|
|
97
|
+
"description": f"项目清单里指向「{label}」的路径(编号 {ref_id})无法被安全访问。这通常是路径写错了,或指向了项目目录之外的位置。",
|
|
98
|
+
"fix_hint": "请检查项目清单中该资源的 path 是否正确,确保它指向项目目录内的文件。",
|
|
99
|
+
"message": raw_message,
|
|
100
|
+
"context": {"involved": [_file_involved(file_type, ref_id, file_path)]},
|
|
101
|
+
"title_key": "inspection.issues.load.pathValidation.title",
|
|
102
|
+
"description_key": "inspection.issues.load.pathValidation.description",
|
|
103
|
+
"fix_hint_key": "inspection.issues.load.pathValidation.fixHint",
|
|
104
|
+
"message_params": {"resourceLabel": label, "refId": ref_id},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def file_not_found_error(file_type: str, ref_id: str, file_path: str) -> dict:
|
|
109
|
+
"""引用的文件不存在。
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
file_type: 文件类型
|
|
113
|
+
ref_id: manifest 中的引用 ID
|
|
114
|
+
file_path: 缺失的文件绝对路径
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
LoadingError 友好字段 dict
|
|
118
|
+
"""
|
|
119
|
+
label = _resource_label(file_type)
|
|
120
|
+
# 文件名单独抽出,避免长路径直接糊在描述里
|
|
121
|
+
filename = os.path.basename(file_path) if file_path else ""
|
|
122
|
+
return {
|
|
123
|
+
"severity": "blocker",
|
|
124
|
+
"title": f"{label}文件找不到了",
|
|
125
|
+
"description": f"项目清单里引用的「{label}」(编号 {ref_id})对应的文件「{filename or file_path}」不存在。可能文件被移动、删除或改名了。",
|
|
126
|
+
"fix_hint": "请确认该文件是否还在,或从项目清单中移除这条已经失效的引用。",
|
|
127
|
+
"message": f"{file_type} 文件不存在: {file_path}",
|
|
128
|
+
"context": {"involved": [_file_involved(file_type, ref_id, file_path)]},
|
|
129
|
+
"title_key": "inspection.issues.load.notFound.title",
|
|
130
|
+
"description_key": "inspection.issues.load.notFound.description",
|
|
131
|
+
"fix_hint_key": "inspection.issues.load.notFound.fixHint",
|
|
132
|
+
"message_params": {"resourceLabel": label, "refId": ref_id, "filename": filename},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def parse_error(file_type: str, ref_id: str, file_path: str, raw_exception: BaseException) -> dict:
|
|
137
|
+
"""文件解析失败(YAML 语法错或 Pydantic 校验错)。
|
|
138
|
+
|
|
139
|
+
把冗长的原始异常归类为两种用户可理解的子类,原始信息保留在 message。
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
file_type: 文件类型
|
|
143
|
+
ref_id: manifest 中的引用 ID
|
|
144
|
+
file_path: 出错文件路径
|
|
145
|
+
raw_exception: 原始异常对象
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
LoadingError 友好字段 dict
|
|
149
|
+
"""
|
|
150
|
+
label = _resource_label(file_type)
|
|
151
|
+
raw_text = str(raw_exception)
|
|
152
|
+
# 区分 YAML 语法错误 vs 字段校验错误,给出不同的修复方向
|
|
153
|
+
is_yaml_syntax = _looks_like_yaml_error(raw_exception, raw_text)
|
|
154
|
+
if is_yaml_syntax:
|
|
155
|
+
fix_hint = "请打开文件检查 YAML 缩进、冒号、引号是否正确,尤其是列表和键值对的格式。"
|
|
156
|
+
desc = (
|
|
157
|
+
f"「{label}」(编号 {ref_id})的文件无法按配置格式解析,通常是 YAML 语法写错了(比如缩进不对、漏了冒号)。"
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
fix_hint = "请打开文件检查必填字段是否完整、取值是否符合规范,参考同类型的其他配置文件。"
|
|
161
|
+
desc = f"「{label}」(编号 {ref_id})的文件格式不对,缺少必要字段或字段取值不符合要求。"
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"severity": "blocker",
|
|
165
|
+
"title": f"{label}文件内容格式有误",
|
|
166
|
+
"description": desc,
|
|
167
|
+
"fix_hint": fix_hint,
|
|
168
|
+
"message": f"{file_type} 文件解析失败: {raw_text}",
|
|
169
|
+
"context": {"involved": [_file_involved(file_type, ref_id, file_path)]},
|
|
170
|
+
"title_key": "inspection.issues.load.parseError.title",
|
|
171
|
+
"description_key": "inspection.issues.load.parseError.description",
|
|
172
|
+
"fix_hint_key": "inspection.issues.load.parseError.fixHint",
|
|
173
|
+
"message_params": {"resourceLabel": label, "refId": ref_id},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def template_expansion_error(instance_id: str, raw_exception: BaseException, file_path: str = "") -> dict:
|
|
178
|
+
"""模板展开失败。
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
instance_id: 模板实例 ID
|
|
182
|
+
raw_exception: 原始异常
|
|
183
|
+
file_path: 模板实例所在配置文件路径(通常是 project.precis.yaml),供 involved 实体展示
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
LoadingError 友好字段 dict
|
|
187
|
+
"""
|
|
188
|
+
return {
|
|
189
|
+
"severity": "blocker",
|
|
190
|
+
"title": "模板无法正常展开",
|
|
191
|
+
"description": f"画布上的模板(编号 {instance_id})在展开成具体规则时出错了。可能是模板参数没填全,或模板定义本身有问题。",
|
|
192
|
+
"fix_hint": "请检查该模板的参数是否完整、引用的列/表是否存在,必要时删除该模板重新创建。",
|
|
193
|
+
"message": f"模板实例 '{instance_id}' 展开失败: {raw_exception}",
|
|
194
|
+
"context": {"involved": [_file_involved("template", instance_id, file_path)]},
|
|
195
|
+
"title_key": "inspection.issues.load.templateExpansion.title",
|
|
196
|
+
"description_key": "inspection.issues.load.templateExpansion.description",
|
|
197
|
+
"fix_hint_key": "inspection.issues.load.templateExpansion.fixHint",
|
|
198
|
+
"message_params": {"instanceId": instance_id},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def template_node_expansion_error(instance_id: str, node_id: str, message: str, file_path: str = "") -> dict:
|
|
203
|
+
"""模板内单个节点展开失败(其余节点正常展开,仅该节点产物缺失)。
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
instance_id: 模板实例 ID
|
|
207
|
+
node_id: 展开失败的模板节点本地 ID
|
|
208
|
+
message: 节点级展开失败的原始异常信息
|
|
209
|
+
file_path: 模板实例所在配置文件路径(通常是 project.precis.yaml),供 involved 实体展示
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
LoadingError 友好字段 dict
|
|
213
|
+
"""
|
|
214
|
+
ref_id = f"{instance_id}/{node_id}"
|
|
215
|
+
return {
|
|
216
|
+
"severity": "warning",
|
|
217
|
+
"title": "模板部分内容未能展开",
|
|
218
|
+
"description": f"模板(编号 {instance_id})中的某个节点(编号 {node_id})在展开成具体规则时出错了,该节点的约束/转换没有生成,其余节点已正常展开。",
|
|
219
|
+
"fix_hint": "请检查该节点的参数配置与引用的列/表是否存在,修正后重新加载项目;缺失的规则在此之前不会参与校验。",
|
|
220
|
+
"message": f"模板实例 '{instance_id}' 的节点 '{node_id}' 展开失败: {message}",
|
|
221
|
+
"context": {"involved": [_file_involved("template", ref_id, file_path)]},
|
|
222
|
+
"title_key": "inspection.issues.load.templateNodeExpansion.title",
|
|
223
|
+
"description_key": "inspection.issues.load.templateNodeExpansion.description",
|
|
224
|
+
"fix_hint_key": "inspection.issues.load.templateNodeExpansion.fixHint",
|
|
225
|
+
"message_params": {"instanceId": instance_id, "nodeId": node_id},
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def manifest_version_missing(supported: str) -> dict:
|
|
230
|
+
"""manifest 顶层缺少 version 字段。
|
|
231
|
+
|
|
232
|
+
P0-1 版本识别:version 字段在 Pydantic 模型中有默认值,缺失会被静默吞掉,
|
|
233
|
+
故在解析前显式检查并在 loading_errors 通道报错。
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
supported: 当前支持的版本号列表描述(如 "2")
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
LoadingError 友好字段 dict
|
|
240
|
+
"""
|
|
241
|
+
return {
|
|
242
|
+
"severity": "blocker",
|
|
243
|
+
"title": "配置文件缺少 version 字段",
|
|
244
|
+
"description": f"项目清单没有写 version 字段,无法识别配置格式。当前支持的版本为 {supported}。",
|
|
245
|
+
"fix_hint": f"请在 project.precis.yaml 顶层添加一行「version: {supported}」。",
|
|
246
|
+
"message": "缺少 version 字段,当前支持版本为 " + supported,
|
|
247
|
+
"context": {"involved": []},
|
|
248
|
+
"title_key": "inspection.issues.load.manifestVersionMissing.title",
|
|
249
|
+
"description_key": "inspection.issues.load.manifestVersionMissing.description",
|
|
250
|
+
"fix_hint_key": "inspection.issues.load.manifestVersionMissing.fixHint",
|
|
251
|
+
"message_params": {"supported": supported},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def manifest_version_unsupported(version: object, supported: str) -> dict:
|
|
256
|
+
"""manifest 顶层 version 字段不是受支持的版本。
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
version: 文件中实际写的 version 值(可能为任意 YAML 类型)
|
|
260
|
+
supported: 当前支持的版本号列表描述(如 "2")
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
LoadingError 友好字段 dict
|
|
264
|
+
"""
|
|
265
|
+
return {
|
|
266
|
+
"severity": "blocker",
|
|
267
|
+
"title": f"配置版本 {version} 不被支持",
|
|
268
|
+
"description": f"项目清单写的版本是 {version!r},当前支持的版本为 {supported}。旧版配置需要先升级才能使用。",
|
|
269
|
+
"fix_hint": (
|
|
270
|
+
f"请将配置升级到 V2 格式:顶层改为「version: {supported}」,schema 使用 columns+source 结构、"
|
|
271
|
+
"约束拆分为 constraints/*.constraint.yaml 独立文件(refs+params 分离)。"
|
|
272
|
+
"可参考仓库 qa_test/qa_simple/ 示例项目对照迁移。"
|
|
273
|
+
),
|
|
274
|
+
"message": f"不支持的项目配置版本: {version!r},当前支持版本为 {supported}",
|
|
275
|
+
"context": {"involved": []},
|
|
276
|
+
"title_key": "inspection.issues.load.manifestVersionUnsupported.title",
|
|
277
|
+
"description_key": "inspection.issues.load.manifestVersionUnsupported.description",
|
|
278
|
+
"fix_hint_key": "inspection.issues.load.manifestVersionUnsupported.fixHint",
|
|
279
|
+
"message_params": {"version": str(version), "supported": supported},
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _looks_like_yaml_error(exc: BaseException, text: str) -> bool:
|
|
284
|
+
"""判断异常是否为 YAML 语法错误(而非字段校验错误)。"""
|
|
285
|
+
# yaml.YAMLError 及其子类
|
|
286
|
+
if exc.__class__.__module__.startswith("yaml"):
|
|
287
|
+
return True
|
|
288
|
+
# 文本特征:YAML 错误通常含 "while parsing"/"mapping"/"expected"/"found character" 等
|
|
289
|
+
lowered = text.lower()
|
|
290
|
+
yaml_markers = (
|
|
291
|
+
"while parsing",
|
|
292
|
+
"mapping values are not allowed",
|
|
293
|
+
"could not determine a constructor",
|
|
294
|
+
"found character that cannot start",
|
|
295
|
+
"expected '<document start>'",
|
|
296
|
+
"while scanning",
|
|
297
|
+
)
|
|
298
|
+
return any(m in lowered for m in yaml_markers)
|