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,386 @@
|
|
|
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
|
+
"""@fileoverview 配置构建器
|
|
17
|
+
|
|
18
|
+
功能概述:
|
|
19
|
+
- 将 LLM 输出、画像数据和现有配置整合为最终的项目配置
|
|
20
|
+
- 生成标准 V2 格式的 manifest、schemas、constraints、regex_nodes
|
|
21
|
+
|
|
22
|
+
输入示例:
|
|
23
|
+
result = build_config(
|
|
24
|
+
project_id="ecommerce",
|
|
25
|
+
project_name="电商数据校验",
|
|
26
|
+
config_path="/path/to/project",
|
|
27
|
+
profiling_data=[...],
|
|
28
|
+
llm_result={"schemas": [...], "constraints": [...]},
|
|
29
|
+
options=GenerationOptions(),
|
|
30
|
+
existing_config=None,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
{"manifest": {...}, "schemas": {...}, "constraints": {...}, "yaml_preview": "..."}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import os
|
|
40
|
+
import re
|
|
41
|
+
from typing import Any
|
|
42
|
+
|
|
43
|
+
import yaml
|
|
44
|
+
|
|
45
|
+
from app.shared.core.utils.path_utils import make_relative, normalize_to_posix
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_config(
|
|
49
|
+
project_id: str,
|
|
50
|
+
project_name: str,
|
|
51
|
+
config_path: str | None,
|
|
52
|
+
profiling_data: list[dict],
|
|
53
|
+
llm_result: dict,
|
|
54
|
+
options: Any,
|
|
55
|
+
existing_config: dict[str, Any] | None,
|
|
56
|
+
) -> dict[str, Any]:
|
|
57
|
+
"""
|
|
58
|
+
@methoddesc 将 LLM 输出、画像数据和现有配置整合为最终的项目配置
|
|
59
|
+
|
|
60
|
+
处理流程:
|
|
61
|
+
1. 如需保留现有配置,先加载已有内容
|
|
62
|
+
2. 根据选项生成/合并 schema、constraint、regex_node
|
|
63
|
+
3. 构建标准 V2 清单文件
|
|
64
|
+
4. 生成 YAML 预览
|
|
65
|
+
|
|
66
|
+
参数:
|
|
67
|
+
project_id: 项目唯一标识
|
|
68
|
+
project_name: 项目显示名称
|
|
69
|
+
config_path: 配置根目录(用于计算相对路径)
|
|
70
|
+
profiling_data: 数据画像结果
|
|
71
|
+
llm_result: LLM 返回的解析结果
|
|
72
|
+
options: 生成选项
|
|
73
|
+
existing_config: 现有配置(keep_existing 时使用)
|
|
74
|
+
|
|
75
|
+
返回:
|
|
76
|
+
包含 manifest、schemas、constraints、regex_nodes 等的字典
|
|
77
|
+
"""
|
|
78
|
+
# 初始化结果容器
|
|
79
|
+
schemas: dict[str, Any] = {}
|
|
80
|
+
constraints: dict[str, Any] = {}
|
|
81
|
+
regex_nodes: dict[str, Any] = {}
|
|
82
|
+
warnings: list[str] = []
|
|
83
|
+
|
|
84
|
+
# 如果有现有配置且 keep_existing,先加载现有内容
|
|
85
|
+
if options.keep_existing and existing_config:
|
|
86
|
+
schemas.update(existing_config.get("schemas", {}))
|
|
87
|
+
constraints.update(existing_config.get("constraints", {}))
|
|
88
|
+
regex_nodes.update(existing_config.get("regex_nodes", {}))
|
|
89
|
+
|
|
90
|
+
# 构建 profiling_data 的 path -> table_name 映射,用于 source 配置
|
|
91
|
+
path_to_profile = {item["path"]: item for item in profiling_data}
|
|
92
|
+
|
|
93
|
+
def _find_existing_schema_id_by_source(src_path: str, sheet_name: str | None) -> str | None:
|
|
94
|
+
"""根据 source.path + source.sheet 查找已有 schema,复用其 ID。
|
|
95
|
+
|
|
96
|
+
避免同一文件因 ID 计算方式不同而被识别为新增 schema。
|
|
97
|
+
"""
|
|
98
|
+
for sid, sdoc in schemas.items():
|
|
99
|
+
existing_src = sdoc.get("source", {})
|
|
100
|
+
existing_path = existing_src.get("path", "")
|
|
101
|
+
existing_sheet = existing_src.get("sheet")
|
|
102
|
+
if existing_path == src_path and existing_sheet == sheet_name:
|
|
103
|
+
return sid
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def _make_relative_path(abs_path: str) -> str:
|
|
107
|
+
"""将绝对路径转为相对于项目根目录的路径。
|
|
108
|
+
|
|
109
|
+
统一使用正斜杠,确保跨平台兼容性。
|
|
110
|
+
"""
|
|
111
|
+
if config_path:
|
|
112
|
+
try:
|
|
113
|
+
return make_relative(config_path, abs_path)
|
|
114
|
+
except ValueError:
|
|
115
|
+
pass
|
|
116
|
+
return normalize_to_posix(os.path.basename(abs_path))
|
|
117
|
+
|
|
118
|
+
def _sanitize_id(name: str) -> str:
|
|
119
|
+
"""将名称转为合法的 id(小写,替换特殊字符为下划线)。"""
|
|
120
|
+
return re.sub(r"[^a-z0-9_]", "_", name.lower()).strip("_")
|
|
121
|
+
|
|
122
|
+
def _generate_semantic_constraint_id(table_id: str, column_ref: str, ctype: str, existing_ids: set) -> str:
|
|
123
|
+
"""生成语义化约束 ID,如遇冲突则自动添加序号后缀。
|
|
124
|
+
|
|
125
|
+
格式: {table_id}_{column_ref}_{ctype} 或 {table_id}_{column_ref}_{ctype}_2
|
|
126
|
+
"""
|
|
127
|
+
base = f"{_sanitize_id(table_id)}_{_sanitize_id(column_ref)}_{_sanitize_id(ctype)}"
|
|
128
|
+
if base not in existing_ids:
|
|
129
|
+
return base
|
|
130
|
+
for i in range(2, 1000):
|
|
131
|
+
candidate = f"{base}_{i}"
|
|
132
|
+
if candidate not in existing_ids:
|
|
133
|
+
return candidate
|
|
134
|
+
return base
|
|
135
|
+
|
|
136
|
+
def _normalize_constraint(cdef: dict, schema_id: str, existing_ids: set) -> dict[str, Any] | None:
|
|
137
|
+
"""将 LLM 输出的约束(简化或 V2 格式)统一转换为 V2 标准格式。
|
|
138
|
+
|
|
139
|
+
支持两种输入:
|
|
140
|
+
- 已有 refs 字段的 V2 格式:做基本校验和补充
|
|
141
|
+
- 简化格式:自动构建 refs 和 params
|
|
142
|
+
"""
|
|
143
|
+
if not isinstance(cdef, dict):
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
# 检测是否已经是 V2 格式(有 refs 字段)
|
|
147
|
+
if "refs" in cdef and isinstance(cdef.get("refs"), dict):
|
|
148
|
+
# 已经是 V2 格式,做基本校验和补充
|
|
149
|
+
cid = cdef.get("id") or _generate_semantic_constraint_id(
|
|
150
|
+
cdef["refs"].get("table_id", schema_id),
|
|
151
|
+
# 显式空 column_ids 列表时 .get 的默认值不生效,需 or 兜底,否则 [0] 越界
|
|
152
|
+
cdef["refs"].get("column_id") or (cdef["refs"].get("column_ids") or ["unknown"])[0],
|
|
153
|
+
cdef.get("type", "unknown"),
|
|
154
|
+
existing_ids,
|
|
155
|
+
)
|
|
156
|
+
existing_ids.add(cid)
|
|
157
|
+
return {
|
|
158
|
+
"version": 2,
|
|
159
|
+
"id": cid,
|
|
160
|
+
"type": cdef.get("type", "NotNull"),
|
|
161
|
+
"enabled": cdef.get("enabled", True),
|
|
162
|
+
"description": cdef.get("description", ""),
|
|
163
|
+
"refs": cdef["refs"],
|
|
164
|
+
"params": cdef.get("params", {}),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# 简化格式转换
|
|
168
|
+
ctype = cdef.get("type", "")
|
|
169
|
+
if not ctype:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
# 统一类型名首字母大写
|
|
173
|
+
ctype_normalized = ctype[0].upper() + ctype[1:].lower() if ctype else "NotNull"
|
|
174
|
+
|
|
175
|
+
table_id = cdef.get("table_id", schema_id)
|
|
176
|
+
column_id = cdef.get("column_id", "")
|
|
177
|
+
column_ids = cdef.get("column_ids", [column_id] if column_id else [])
|
|
178
|
+
|
|
179
|
+
# 构建 refs
|
|
180
|
+
refs: dict[str, Any] = {"table_id": table_id}
|
|
181
|
+
if ctype_normalized == "Unique":
|
|
182
|
+
refs["column_ids"] = column_ids if column_ids else [column_id]
|
|
183
|
+
elif ctype_normalized == "ForeignKey":
|
|
184
|
+
refs["from_table_id"] = cdef.get("from_table_id", table_id)
|
|
185
|
+
refs["from_column_id"] = cdef.get("from_column_id", column_id)
|
|
186
|
+
refs["to_table_id"] = cdef.get("to_table_id", "")
|
|
187
|
+
refs["to_column_id"] = cdef.get("to_column_id", "")
|
|
188
|
+
elif ctype_normalized == "Conditional":
|
|
189
|
+
refs["table_id"] = table_id
|
|
190
|
+
refs["then_column_id"] = cdef.get("then_column_id", column_id)
|
|
191
|
+
refs["if_conditions"] = cdef.get("if_conditions", [])
|
|
192
|
+
refs["if_logic"] = cdef.get("if_logic", "and")
|
|
193
|
+
else:
|
|
194
|
+
refs["column_id"] = column_id
|
|
195
|
+
|
|
196
|
+
# 构建 params
|
|
197
|
+
params: dict[str, Any] = {}
|
|
198
|
+
if ctype_normalized == "AllowedValues":
|
|
199
|
+
params["allowed_values"] = cdef.get("allowed_values", [])
|
|
200
|
+
elif ctype_normalized == "Range":
|
|
201
|
+
params["min"] = cdef.get("min")
|
|
202
|
+
params["max"] = cdef.get("max")
|
|
203
|
+
elif ctype_normalized == "Conditional":
|
|
204
|
+
params["then_value"] = cdef.get("then_value")
|
|
205
|
+
elif ctype_normalized == "Scripted":
|
|
206
|
+
params["expression"] = cdef.get("expression", "")
|
|
207
|
+
|
|
208
|
+
# 表级约束(无 column_id/column_ids)时以 "unknown" 兜底参与 ID 生成,
|
|
209
|
+
# 避免空列表 [0] 索引 IndexError 崩掉整个配置生成
|
|
210
|
+
cid = _generate_semantic_constraint_id(
|
|
211
|
+
table_id, column_id or (column_ids[0] if column_ids else "unknown"), ctype_normalized, existing_ids
|
|
212
|
+
)
|
|
213
|
+
existing_ids.add(cid)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"version": 2,
|
|
217
|
+
"id": cid,
|
|
218
|
+
"type": ctype_normalized,
|
|
219
|
+
"enabled": True,
|
|
220
|
+
"description": cdef.get("description", ""),
|
|
221
|
+
"refs": refs,
|
|
222
|
+
"params": params,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
def _normalize_regex_node(rdef: dict, existing_ids: set) -> dict[str, Any] | None:
|
|
226
|
+
"""将 LLM 输出的正则节点标准化为 V2 格式。
|
|
227
|
+
|
|
228
|
+
如果缺少 ID,则根据名称自动生成;如遇 ID 冲突则添加序号后缀。
|
|
229
|
+
"""
|
|
230
|
+
if not isinstance(rdef, dict):
|
|
231
|
+
return None
|
|
232
|
+
rid = rdef.get("id")
|
|
233
|
+
if not rid:
|
|
234
|
+
rid = f"regex_{_sanitize_id(rdef.get('name', 'unnamed'))}"
|
|
235
|
+
if rid in existing_ids:
|
|
236
|
+
for i in range(2, 1000):
|
|
237
|
+
candidate = f"{rid}_{i}"
|
|
238
|
+
if candidate not in existing_ids:
|
|
239
|
+
rid = candidate
|
|
240
|
+
break
|
|
241
|
+
existing_ids.add(rid)
|
|
242
|
+
return {
|
|
243
|
+
"version": 2,
|
|
244
|
+
"id": rid,
|
|
245
|
+
"name": rdef.get("name", rid),
|
|
246
|
+
"description": rdef.get("description", ""),
|
|
247
|
+
"pattern": rdef.get("pattern", ""),
|
|
248
|
+
"match_mode": rdef.get("match_mode", "full"),
|
|
249
|
+
"case_sensitive": rdef.get("case_sensitive", False),
|
|
250
|
+
"flags": rdef.get("flags", ""),
|
|
251
|
+
"enabled": rdef.get("enabled", True),
|
|
252
|
+
"parameters": [],
|
|
253
|
+
"rules": [],
|
|
254
|
+
"source_ref": rdef.get("source_ref", {}),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# 处理 Schema —— 使用 LLM 提供的语义化 ID
|
|
258
|
+
llm_id_to_schema_id: dict[str, str] = {} # LLM 返回的原始 ID -> 最终 ID 映射
|
|
259
|
+
used_schema_ids: set[str] = set(schemas.keys()) # 已占用的 ID(含 keep_existing)
|
|
260
|
+
if options.generate_schemas:
|
|
261
|
+
for schema_def in llm_result.get("schemas", []):
|
|
262
|
+
if not isinstance(schema_def, dict):
|
|
263
|
+
continue
|
|
264
|
+
llm_schema_id = schema_def.get("id") or schema_def.get("name", "")
|
|
265
|
+
if not llm_schema_id:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# 补充 source 配置
|
|
269
|
+
profile = path_to_profile.get(schema_def.get("_source_path", ""))
|
|
270
|
+
if not profile:
|
|
271
|
+
for p in profiling_data:
|
|
272
|
+
if p["table_name"] == schema_def.get("name", llm_schema_id):
|
|
273
|
+
profile = p
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
source: dict[str, Any] = {"mode": "relative_file", "path": "", "header_row": 0}
|
|
277
|
+
if profile:
|
|
278
|
+
source["path"] = _make_relative_path(profile["path"])
|
|
279
|
+
if profile.get("sheet_name"):
|
|
280
|
+
source["sheet"] = profile["sheet_name"]
|
|
281
|
+
ext = os.path.splitext(profile["path"])[1].lower()
|
|
282
|
+
if ext == ".csv":
|
|
283
|
+
source["options"] = {"delimiter": ",", "encoding": "utf-8"}
|
|
284
|
+
elif ext in [".xlsx", ".xls"]:
|
|
285
|
+
source["options"] = {"engine": "openpyxl"}
|
|
286
|
+
elif ext in [".json", ".jsonl"]:
|
|
287
|
+
source["options"] = {"format": "auto"}
|
|
288
|
+
|
|
289
|
+
# 优先复用已有 schema 的 ID(按 source.path + sheet 匹配)
|
|
290
|
+
src_path = source.get("path", "")
|
|
291
|
+
sheet_name = source.get("sheet")
|
|
292
|
+
existing_id = _find_existing_schema_id_by_source(src_path, sheet_name) if src_path else None
|
|
293
|
+
if existing_id:
|
|
294
|
+
proper_schema_id = existing_id
|
|
295
|
+
else:
|
|
296
|
+
# 使用 LLM 提供的语义化 ID,清洗并保证唯一
|
|
297
|
+
proper_schema_id = _sanitize_id(llm_schema_id)
|
|
298
|
+
base = proper_schema_id
|
|
299
|
+
i = 2
|
|
300
|
+
while proper_schema_id in used_schema_ids:
|
|
301
|
+
proper_schema_id = f"{base}_{i}"
|
|
302
|
+
i += 1
|
|
303
|
+
used_schema_ids.add(proper_schema_id)
|
|
304
|
+
|
|
305
|
+
llm_id_to_schema_id[llm_schema_id] = proper_schema_id
|
|
306
|
+
|
|
307
|
+
# 构建标准 schema 格式
|
|
308
|
+
schema_doc = {
|
|
309
|
+
"version": 2,
|
|
310
|
+
"id": proper_schema_id,
|
|
311
|
+
"name": schema_def.get("name", llm_schema_id),
|
|
312
|
+
"source": schema_def.get("source", source),
|
|
313
|
+
"columns": schema_def.get("columns", []),
|
|
314
|
+
"constraints": [],
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# 处理内联约束(schema 级别的 constraints)
|
|
318
|
+
for cdef in schema_def.get("constraints", []):
|
|
319
|
+
if isinstance(cdef, dict):
|
|
320
|
+
schema_doc["constraints"].append(cdef)
|
|
321
|
+
|
|
322
|
+
schemas[proper_schema_id] = schema_doc
|
|
323
|
+
|
|
324
|
+
def _remap_schema_refs(obj: Any) -> Any:
|
|
325
|
+
"""递归替换约束中的 table_id 为规范 Schema ID。"""
|
|
326
|
+
if isinstance(obj, dict):
|
|
327
|
+
remapped: dict[str, Any] = {}
|
|
328
|
+
for k, v in obj.items():
|
|
329
|
+
if (
|
|
330
|
+
k in ("table_id", "from_table_id", "to_table_id")
|
|
331
|
+
and isinstance(v, str)
|
|
332
|
+
and v in llm_id_to_schema_id
|
|
333
|
+
):
|
|
334
|
+
remapped[k] = llm_id_to_schema_id[v]
|
|
335
|
+
else:
|
|
336
|
+
remapped[k] = _remap_schema_refs(v)
|
|
337
|
+
return remapped
|
|
338
|
+
if isinstance(obj, list):
|
|
339
|
+
return [_remap_schema_refs(item) for item in obj]
|
|
340
|
+
return obj
|
|
341
|
+
|
|
342
|
+
# 先处理独立 Constraints,再统一做 ID 重映射
|
|
343
|
+
if options.generate_constraints:
|
|
344
|
+
constraint_ids = set(constraints.keys())
|
|
345
|
+
for cdef in llm_result.get("constraints", []):
|
|
346
|
+
# 将约束中的 table_id 从 LLM ID 替换为规范 ID
|
|
347
|
+
cdef = _remap_schema_refs(cdef)
|
|
348
|
+
normalized = _normalize_constraint(cdef, "", constraint_ids)
|
|
349
|
+
if normalized:
|
|
350
|
+
constraints[normalized["id"]] = normalized
|
|
351
|
+
else:
|
|
352
|
+
warnings.append(f"无法解析约束: {cdef}")
|
|
353
|
+
|
|
354
|
+
# 对 schema 内联约束也做 ID 重映射
|
|
355
|
+
for schema_doc in schemas.values():
|
|
356
|
+
schema_doc["constraints"] = [
|
|
357
|
+
_remap_schema_refs(c) for c in schema_doc.get("constraints", []) if isinstance(c, dict)
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
# 处理 Regex Nodes
|
|
361
|
+
if options.generate_regex_nodes:
|
|
362
|
+
regex_ids = set(regex_nodes.keys())
|
|
363
|
+
for rdef in llm_result.get("regex_nodes", []):
|
|
364
|
+
normalized = _normalize_regex_node(rdef, regex_ids)
|
|
365
|
+
if normalized:
|
|
366
|
+
regex_nodes[normalized["id"]] = normalized
|
|
367
|
+
|
|
368
|
+
# 构建 Manifest
|
|
369
|
+
manifest = {"version": 2, "project": {"id": project_id, "name": project_name}}
|
|
370
|
+
manifest["schemas"] = [{"id": sid, "path": f"schemas/{sid}.schema.yaml"} for sid in schemas]
|
|
371
|
+
manifest["constraints"] = [{"id": cid, "path": f"constraints/{cid}.constraint.yaml"} for cid in constraints]
|
|
372
|
+
manifest["regex_nodes"] = [{"id": rid, "path": f"regex/{rid}.regex.yaml"} for rid in regex_nodes]
|
|
373
|
+
|
|
374
|
+
# 生成 YAML 预览
|
|
375
|
+
preview = {"manifest": manifest, "schemas": schemas, "constraints": constraints, "regex_nodes": regex_nodes}
|
|
376
|
+
yaml_preview = yaml.safe_dump(preview, sort_keys=False, allow_unicode=True)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"success": True,
|
|
380
|
+
"yaml_preview": yaml_preview,
|
|
381
|
+
"manifest": manifest,
|
|
382
|
+
"schemas": schemas,
|
|
383
|
+
"constraints": constraints,
|
|
384
|
+
"regex_nodes": regex_nodes,
|
|
385
|
+
"warnings": warnings,
|
|
386
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
"""@fileoverview 配置生成异常定义
|
|
17
|
+
|
|
18
|
+
生成链路各子模块(响应解析/数据剖析/Agent 装配)共享的异常类型,
|
|
19
|
+
独立成模块以避免 service ↔ 子模块循环导入;service.py 对外重导出保持既有导入路径不变。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GenerationParseError(Exception):
|
|
26
|
+
"""LLM 响应解析失败"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, message: str, raw_content: str = ""):
|
|
29
|
+
super().__init__(message)
|
|
30
|
+
self.raw_content = raw_content
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CancelledError(Exception):
|
|
34
|
+
"""生成被取消"""
|
|
35
|
+
|
|
36
|
+
pass
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
"""@fileoverview 既有项目配置加载(keep_existing 合并基线)
|
|
17
|
+
|
|
18
|
+
读取项目清单文件以及其引用的 schema、constraint、regex 文件,组装成
|
|
19
|
+
供 build_config 合并使用的既有配置字典。读取失败仅记警告、按条跳过,
|
|
20
|
+
保证生成流程对局部损坏的项目配置保持宽容。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from app.shared.core.io.yaml import read_yaml
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_existing_config(config_path: str) -> dict[str, Any] | None:
|
|
36
|
+
"""加载现有配置(用于 keep_existing 合并)。
|
|
37
|
+
|
|
38
|
+
读取项目清单文件以及引用的 schema、constraint、regex 文件。
|
|
39
|
+
|
|
40
|
+
参数:
|
|
41
|
+
config_path: 项目配置目录(含 project.precis.yaml)
|
|
42
|
+
|
|
43
|
+
返回:
|
|
44
|
+
{"manifest": ..., "schemas": {...}, "constraints": {...}, "regex_nodes": {...}};
|
|
45
|
+
清单不存在或整体读取失败时返回 None
|
|
46
|
+
"""
|
|
47
|
+
manifest_path = os.path.join(config_path, "project.precis.yaml")
|
|
48
|
+
if not os.path.isfile(manifest_path):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
manifest_data = read_yaml(Path(manifest_path))
|
|
53
|
+
if not isinstance(manifest_data, dict):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
existing: dict[str, Any] = {
|
|
57
|
+
"manifest": manifest_data,
|
|
58
|
+
"schemas": {},
|
|
59
|
+
"constraints": {},
|
|
60
|
+
"regex_nodes": {},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# 读取现有 schema 文件
|
|
64
|
+
for ref in manifest_data.get("schemas", []):
|
|
65
|
+
schema_path = os.path.join(config_path, ref.get("path", ""))
|
|
66
|
+
if os.path.isfile(schema_path):
|
|
67
|
+
try:
|
|
68
|
+
raw = read_yaml(Path(schema_path))
|
|
69
|
+
if isinstance(raw, dict):
|
|
70
|
+
existing["schemas"][ref["id"]] = raw
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning(f"读取现有 schema 文件失败 {schema_path}: {e}")
|
|
73
|
+
|
|
74
|
+
# 读取现有 constraint 文件
|
|
75
|
+
for ref in manifest_data.get("constraints", []):
|
|
76
|
+
constraint_path = os.path.join(config_path, ref.get("path", ""))
|
|
77
|
+
if os.path.isfile(constraint_path):
|
|
78
|
+
try:
|
|
79
|
+
raw = read_yaml(Path(constraint_path))
|
|
80
|
+
if isinstance(raw, dict):
|
|
81
|
+
existing["constraints"][ref["id"]] = raw
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.warning(f"读取现有 constraint 文件失败 {constraint_path}: {e}")
|
|
84
|
+
|
|
85
|
+
# 读取现有 regex 文件
|
|
86
|
+
for ref in manifest_data.get("regex_nodes", []):
|
|
87
|
+
regex_path = os.path.join(config_path, ref.get("path", ""))
|
|
88
|
+
if os.path.isfile(regex_path):
|
|
89
|
+
try:
|
|
90
|
+
raw = read_yaml(Path(regex_path))
|
|
91
|
+
if isinstance(raw, dict):
|
|
92
|
+
existing["regex_nodes"][ref["id"]] = raw
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.warning(f"读取现有 regex 文件失败 {regex_path}: {e}")
|
|
95
|
+
|
|
96
|
+
return existing
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.warning(f"加载现有配置失败: {e}")
|
|
99
|
+
return None
|