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,196 @@
|
|
|
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
|
+
- 提供基于行内数据(TransformOutput / ManualData 节点)的校验接口
|
|
21
|
+
- 直接接收二维数组行数据,无需文件路径
|
|
22
|
+
- 支持多种校验类型(唯一性、非空、允许值、区间、字符集、脚本、外键等)
|
|
23
|
+
- 通过 UnifiedValidationService 统一调度各类校验逻辑
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 基于 FastAPI APIRouter 组织路由,挂载到 /validation 前缀下
|
|
27
|
+
- 将行数据转为 pandas DataFrame,复用 execute_dataframe_validation 流水线
|
|
28
|
+
- 统一返回 ValidationResponse 标准化响应结构
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
POST /validate/inline
|
|
32
|
+
Body:
|
|
33
|
+
{
|
|
34
|
+
"validation_type": "not_null",
|
|
35
|
+
"target_column_name": "name",
|
|
36
|
+
"rows": [["name", "age"], ["Alice", "25"], ["", "30"], ["Bob", ""]]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
输出示例:
|
|
40
|
+
{
|
|
41
|
+
"success": true,
|
|
42
|
+
"validation_type": "not_null",
|
|
43
|
+
"data": {
|
|
44
|
+
"is_valid": false,
|
|
45
|
+
"error_count": 1,
|
|
46
|
+
"total_rows": 3,
|
|
47
|
+
"error_rows": [{"row_index": 1, "cell_value": "", "error_message": null}],
|
|
48
|
+
"validation_time": "0.001s"
|
|
49
|
+
},
|
|
50
|
+
"error": null
|
|
51
|
+
}
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
import logging
|
|
57
|
+
|
|
58
|
+
import pandas as pd
|
|
59
|
+
|
|
60
|
+
from app.api.models import InlineValidationRequest, ValidationResponse
|
|
61
|
+
from app.api.routers.validation.common import execute_dataframe_validation
|
|
62
|
+
|
|
63
|
+
from .router import router
|
|
64
|
+
|
|
65
|
+
logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _infer_column_type(series: pd.Series) -> str | None:
|
|
69
|
+
"""推断行内数据列的类型,无 Schema 声明时辅助数值比较等场景。
|
|
70
|
+
|
|
71
|
+
规则:
|
|
72
|
+
- 所有非空值均为整数格式时返回 "integer"
|
|
73
|
+
- 所有非空值均为数值格式(含小数)时返回 "decimal"
|
|
74
|
+
- 已经是 pandas 数值类型时直接返回对应类型
|
|
75
|
+
- 其他情况返回 None,保持原始字符串类型
|
|
76
|
+
"""
|
|
77
|
+
if series.empty:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
non_null = series.dropna()
|
|
81
|
+
if non_null.empty:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
# 已经是 pandas 数值类型
|
|
85
|
+
if pd.api.types.is_numeric_dtype(non_null):
|
|
86
|
+
return "integer" if pd.api.types.is_integer_dtype(non_null) else "decimal"
|
|
87
|
+
|
|
88
|
+
str_values = non_null.astype(str)
|
|
89
|
+
|
|
90
|
+
# 整数格式
|
|
91
|
+
if str_values.str.fullmatch(r"^-?\d+$").all():
|
|
92
|
+
return "integer"
|
|
93
|
+
|
|
94
|
+
# 数值格式(含小数)
|
|
95
|
+
numeric = pd.to_numeric(str_values, errors="coerce")
|
|
96
|
+
if numeric.notna().all():
|
|
97
|
+
return "decimal"
|
|
98
|
+
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@router.post(
|
|
103
|
+
"/validate/inline",
|
|
104
|
+
response_model=ValidationResponse,
|
|
105
|
+
summary="基于行内数据的校验(Inline 模式)",
|
|
106
|
+
# 行为约定:本端点永远返回 200,业务失败以 success=false + error 字段表达,不产生 500
|
|
107
|
+
responses={
|
|
108
|
+
200: {
|
|
109
|
+
"description": "校验已执行;业务失败(行数据为空、列不存在、校验异常等)同样返回 200,以 success=false + error 字段表达",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
def validate_data_inline(request: InlineValidationRequest) -> ValidationResponse:
|
|
114
|
+
"""
|
|
115
|
+
基于行内数据的校验接口(Inline 模式)。
|
|
116
|
+
|
|
117
|
+
专为前端纯数据节点(TransformOutput / ManualData)设计,
|
|
118
|
+
直接接收二维行数据数组,转换为 DataFrame 后复用统一校验流水线。
|
|
119
|
+
|
|
120
|
+
参数:
|
|
121
|
+
request (InlineValidationRequest): 校验请求体,包含行数据、校验类型、目标列、配置等
|
|
122
|
+
|
|
123
|
+
返回值:
|
|
124
|
+
ValidationResponse: 标准化校验响应
|
|
125
|
+
|
|
126
|
+
业务逻辑:
|
|
127
|
+
1. 验证 rows 数据不为空
|
|
128
|
+
2. 根据是否提供 column_names,决定表头来源(第一行 or column_names)
|
|
129
|
+
3. 构建 pandas DataFrame
|
|
130
|
+
4. 对目标列做类型推断(与文件加载行为保持一致)
|
|
131
|
+
5. 调用 execute_dataframe_validation 执行校验
|
|
132
|
+
6. 捕获异常并返回友好错误信息
|
|
133
|
+
|
|
134
|
+
错误语义:
|
|
135
|
+
所有业务失败与意外异常(行数据为空、列不存在、校验出错等)均在本端点内
|
|
136
|
+
捕获并转为 success=false + error 字段随 HTTP 200 返回,不抛出 500。
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
# 验证行数据不为空
|
|
140
|
+
if not request.rows:
|
|
141
|
+
return ValidationResponse(
|
|
142
|
+
success=False,
|
|
143
|
+
validation_type=request.validation_type,
|
|
144
|
+
data=None,
|
|
145
|
+
error="行数据不能为空",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# 构建 DataFrame:根据 column_names 决定表头来源
|
|
149
|
+
if request.column_names:
|
|
150
|
+
# column_names 已提供:所有 rows 均为数据行
|
|
151
|
+
df = pd.DataFrame(request.rows, columns=request.column_names)
|
|
152
|
+
else:
|
|
153
|
+
# 默认:第一行为表头,其余为数据行
|
|
154
|
+
if len(request.rows) < 2:
|
|
155
|
+
return ValidationResponse(
|
|
156
|
+
success=False,
|
|
157
|
+
validation_type=request.validation_type,
|
|
158
|
+
data=None,
|
|
159
|
+
error="行数据至少需要包含表头行和一行数据",
|
|
160
|
+
)
|
|
161
|
+
header = [str(col) for col in request.rows[0]]
|
|
162
|
+
df = pd.DataFrame(request.rows[1:], columns=header)
|
|
163
|
+
|
|
164
|
+
logger.info(
|
|
165
|
+
f"[INLINE] 收到行内校验请求: type={request.validation_type}, "
|
|
166
|
+
f"column={request.target_column_name}, rows={len(df)}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# 委托给共享流水线执行校验
|
|
170
|
+
# 传递 column_data_type 让后端按 Schema 类型转换,保持与全量校验一致
|
|
171
|
+
# 若请求未提供类型,则根据目标列内容自动推断数值类型,避免行内数字字符串
|
|
172
|
+
# 在脚本/区间等数值比较场景下因类型错误而失败
|
|
173
|
+
column_data_type = request.column_data_type
|
|
174
|
+
if not column_data_type and request.target_column_name in df.columns:
|
|
175
|
+
column_data_type = _infer_column_type(df[request.target_column_name])
|
|
176
|
+
|
|
177
|
+
return execute_dataframe_validation(
|
|
178
|
+
df=df,
|
|
179
|
+
validation_type=request.validation_type,
|
|
180
|
+
target_column_name=request.target_column_name,
|
|
181
|
+
validation_config=request.validation_config,
|
|
182
|
+
allow_unsafe_eval=request.allow_unsafe_eval,
|
|
183
|
+
column_data_type=column_data_type,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"[INLINE] 行内校验异常: {str(e)}")
|
|
188
|
+
import traceback
|
|
189
|
+
|
|
190
|
+
traceback.print_exc()
|
|
191
|
+
return ValidationResponse(
|
|
192
|
+
success=False,
|
|
193
|
+
validation_type=request.validation_type,
|
|
194
|
+
data=None,
|
|
195
|
+
error=f"行内校验过程中发生错误: {str(e)}",
|
|
196
|
+
)
|
|
@@ -0,0 +1,314 @@
|
|
|
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
|
+
- 提供基于文件路径的数据校验接口
|
|
21
|
+
- 支持多种校验类型(唯一性、非空、允许值、外键、正则等)
|
|
22
|
+
- 支持正则表达式校验(含匹配模式、大小写敏感等参数)
|
|
23
|
+
- 支持批量校验请求处理
|
|
24
|
+
- 通过 UnifiedValidationService 统一调度各类校验逻辑
|
|
25
|
+
|
|
26
|
+
架构设计:
|
|
27
|
+
- 基于 FastAPI APIRouter 组织路由,挂载到 /validation 前缀下
|
|
28
|
+
- 直接通过文件路径加载数据,无需上传文件
|
|
29
|
+
- 统一返回 ValidationResponse / RegexValidationResponse 标准化响应结构
|
|
30
|
+
|
|
31
|
+
输入示例:
|
|
32
|
+
POST /validate/path
|
|
33
|
+
Body:
|
|
34
|
+
{
|
|
35
|
+
"source_file_path": "/data/users.xlsx",
|
|
36
|
+
"validation_type": "Unique",
|
|
37
|
+
"target_column_name": "email",
|
|
38
|
+
"sheet_name": "Sheet1",
|
|
39
|
+
"header_row": 0,
|
|
40
|
+
"validation_config": {"case_sensitive": true}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
输出示例:
|
|
44
|
+
{
|
|
45
|
+
"success": true,
|
|
46
|
+
"validation_type": "Unique",
|
|
47
|
+
"data": {
|
|
48
|
+
"is_valid": false,
|
|
49
|
+
"error_count": 3,
|
|
50
|
+
"total_rows": 100,
|
|
51
|
+
"match_count": null,
|
|
52
|
+
"error_rows": [{"row_index": 5, "cell_value": "dup@example.com", "error_message": "重复值"}],
|
|
53
|
+
"validation_time": 0.12
|
|
54
|
+
},
|
|
55
|
+
"error": null
|
|
56
|
+
}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import logging
|
|
62
|
+
from typing import Any
|
|
63
|
+
|
|
64
|
+
from fastapi import HTTPException
|
|
65
|
+
|
|
66
|
+
from app.api.models import (
|
|
67
|
+
RegexValidationRequest,
|
|
68
|
+
RegexValidationResponse,
|
|
69
|
+
ValidationRequest,
|
|
70
|
+
ValidationResponse,
|
|
71
|
+
)
|
|
72
|
+
from app.api.routers.validation.common import (
|
|
73
|
+
convert_validation_result_to_regex,
|
|
74
|
+
execute_standard_validation,
|
|
75
|
+
)
|
|
76
|
+
from app.shared.services.preview.path_validation import validate_file_access
|
|
77
|
+
from app.shared.services.validation import UnifiedValidationService, ValidationType, load_file_data
|
|
78
|
+
|
|
79
|
+
from .router import router
|
|
80
|
+
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@router.post(
|
|
85
|
+
"/validate/path",
|
|
86
|
+
response_model=ValidationResponse,
|
|
87
|
+
summary="基于文件路径的单条数据校验(Path 模式)",
|
|
88
|
+
# 行为约定:本端点永远返回 200,业务失败以 success=false + error 字段表达,不产生 404/500
|
|
89
|
+
responses={
|
|
90
|
+
200: {
|
|
91
|
+
"description": "校验已执行;业务失败(文件未找到、路径非法、校验异常等)同样返回 200,以 success=false + error 字段表达",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
def validate_data_with_path(request: ValidationRequest) -> ValidationResponse:
|
|
96
|
+
"""
|
|
97
|
+
基于文件路径的单条数据校验接口(Path 模式)。
|
|
98
|
+
|
|
99
|
+
参数:
|
|
100
|
+
request (ValidationRequest): 校验请求体,包含文件路径、校验类型、目标列、配置等
|
|
101
|
+
|
|
102
|
+
返回值:
|
|
103
|
+
ValidationResponse: 标准化校验响应
|
|
104
|
+
|
|
105
|
+
业务逻辑:
|
|
106
|
+
1. 记录基本信息日志
|
|
107
|
+
2. 校验文件路径是否为空
|
|
108
|
+
3. 调用 validate_file_path 校验路径格式合法性
|
|
109
|
+
4. 检查文件是否在磁盘上真实存在
|
|
110
|
+
5. 确定表头行号并加载数据文件
|
|
111
|
+
6. 检查目标列是否存在
|
|
112
|
+
7. 调用 UnifiedValidationService.validate 执行具体校验
|
|
113
|
+
8. 转换错误行数据并组装响应
|
|
114
|
+
9. 记录校验完成状态,捕获并处理异常
|
|
115
|
+
|
|
116
|
+
错误语义:
|
|
117
|
+
所有业务失败与意外异常(文件未找到、路径非法、校验出错等)均在本端点内
|
|
118
|
+
捕获并转为 success=false + error 字段随 HTTP 200 返回,不抛出 404/500。
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
# 记录收到的路径校验请求信息,便于排查问题
|
|
122
|
+
logger.info(f"[VALIDATION] 收到路径校验请求: {request.source_file_path}")
|
|
123
|
+
logger.info(f"[VALIDATION] 校验类型: {request.validation_type}, 目标列: {request.target_column_name}")
|
|
124
|
+
|
|
125
|
+
source_file_path = request.source_file_path
|
|
126
|
+
|
|
127
|
+
# 检查文件路径是否为空,为空则直接返回错误
|
|
128
|
+
if not source_file_path:
|
|
129
|
+
return ValidationResponse(
|
|
130
|
+
success=False, validation_type=request.validation_type, data=None, error="文件路径不能为空"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# 校验文件路径格式是否合法(如防止路径遍历攻击)
|
|
134
|
+
validate_file_access(source_file_path)
|
|
135
|
+
|
|
136
|
+
response = execute_standard_validation(
|
|
137
|
+
source_file_path=source_file_path,
|
|
138
|
+
sheet_name=request.sheet_name,
|
|
139
|
+
header_row=request.header_row,
|
|
140
|
+
validation_type=request.validation_type,
|
|
141
|
+
target_column_name=request.target_column_name,
|
|
142
|
+
validation_config=request.validation_config,
|
|
143
|
+
column_data_type=request.column_data_type,
|
|
144
|
+
source_config=request.to_source_config(),
|
|
145
|
+
# B14 修复:透传 allow_unsafe_eval,过去遗漏导致 Path 模式下脚本约束恒为 False
|
|
146
|
+
allow_unsafe_eval=request.allow_unsafe_eval,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# 记录校验完成状态
|
|
150
|
+
if response.success and response.data:
|
|
151
|
+
logger.info(f"[VALIDATION] 路径校验完成: 成功={response.data.is_valid}, 错误数={response.data.error_count}")
|
|
152
|
+
|
|
153
|
+
return response
|
|
154
|
+
|
|
155
|
+
except FileNotFoundError as e:
|
|
156
|
+
return ValidationResponse(success=False, validation_type=request.validation_type, data=None, error=str(e))
|
|
157
|
+
except HTTPException as e:
|
|
158
|
+
return ValidationResponse(success=False, validation_type=request.validation_type, data=None, error=e.detail)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
# 捕获所有未预期异常,记录详细堆栈并返回友好错误信息
|
|
161
|
+
logger.error(f"[VALIDATION] 路径校验异常: {str(e)}")
|
|
162
|
+
import traceback
|
|
163
|
+
|
|
164
|
+
traceback.print_exc()
|
|
165
|
+
return ValidationResponse(
|
|
166
|
+
success=False,
|
|
167
|
+
validation_type=request.validation_type,
|
|
168
|
+
data=None,
|
|
169
|
+
error=f"校验过程中发生错误: {str(e)}",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@router.post(
|
|
174
|
+
"/regex/path",
|
|
175
|
+
response_model=RegexValidationResponse,
|
|
176
|
+
summary="基于文件路径的正则表达式校验(Path 模式)",
|
|
177
|
+
# 行为约定:本端点永远返回 200,业务失败以 success=false + error 字段表达,不产生 404/500
|
|
178
|
+
responses={
|
|
179
|
+
200: {
|
|
180
|
+
"description": "校验已执行;业务失败(文件未找到、路径非法、校验异常等)同样返回 200,以 success=false + error 字段表达",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
def validate_regex_with_path(request: RegexValidationRequest) -> RegexValidationResponse:
|
|
185
|
+
"""
|
|
186
|
+
基于文件路径的正则表达式校验接口(Path 模式)。
|
|
187
|
+
|
|
188
|
+
参数:
|
|
189
|
+
request (RegexValidationRequest): 正则校验请求体,包含文件路径、正则模式、
|
|
190
|
+
匹配模式、大小写敏感设置、目标列等
|
|
191
|
+
|
|
192
|
+
返回值:
|
|
193
|
+
RegexValidationResponse: 正则校验专用响应模型
|
|
194
|
+
|
|
195
|
+
业务逻辑:
|
|
196
|
+
1. 记录基本信息日志
|
|
197
|
+
2. 校验文件路径是否为空
|
|
198
|
+
3. 调用 validate_file_path 校验路径格式合法性
|
|
199
|
+
4. 检查文件是否在磁盘上真实存在
|
|
200
|
+
5. 加载数据文件,支持自定义表头配置
|
|
201
|
+
6. 检查目标列是否存在
|
|
202
|
+
7. 调用 UnifiedValidationService.validate 执行 REGEX 类型校验
|
|
203
|
+
8. 使用 convert_validation_result_to_regex 转换结果
|
|
204
|
+
9. 记录校验完成状态,返回响应
|
|
205
|
+
|
|
206
|
+
错误语义:
|
|
207
|
+
所有业务失败与意外异常(文件未找到、路径非法、校验出错等)均在本端点内
|
|
208
|
+
捕获并转为 success=false + error 字段随 HTTP 200 返回,不抛出 404/500。
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
# 记录收到的路径正则校验请求信息
|
|
212
|
+
logger.info(f"[REGEX] 收到路径正则校验请求: {request.source_file_path}")
|
|
213
|
+
logger.info(f"[REGEX] 正则模式: {request.regex_pattern}, 目标列: {request.target_column_name}")
|
|
214
|
+
|
|
215
|
+
source_file_path = request.source_file_path
|
|
216
|
+
|
|
217
|
+
# 检查文件路径是否为空
|
|
218
|
+
if not source_file_path:
|
|
219
|
+
return RegexValidationResponse(success=False, data=None, error="文件路径不能为空")
|
|
220
|
+
|
|
221
|
+
# 校验文件路径格式合法性
|
|
222
|
+
validate_file_access(source_file_path)
|
|
223
|
+
|
|
224
|
+
# 加载数据文件,支持自定义表头
|
|
225
|
+
df = load_file_data(
|
|
226
|
+
source_file_path=source_file_path,
|
|
227
|
+
sheet_name=request.sheet_name,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# 检查目标列是否存在于数据中
|
|
231
|
+
if request.target_column_name not in df.columns:
|
|
232
|
+
return RegexValidationResponse(success=False, data=None, error=f"未找到列: {request.target_column_name}")
|
|
233
|
+
|
|
234
|
+
# 调用统一校验服务执行正则校验
|
|
235
|
+
result = UnifiedValidationService.validate(
|
|
236
|
+
validation_type=ValidationType.REGEX,
|
|
237
|
+
df=df,
|
|
238
|
+
column=request.target_column_name,
|
|
239
|
+
regex_pattern=request.regex_pattern,
|
|
240
|
+
regex_flags=request.regex_flags or "",
|
|
241
|
+
match_mode=request.match_mode,
|
|
242
|
+
case_sensitive=request.case_sensitive,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# 将通用校验结果转换为正则专用响应模型
|
|
246
|
+
regex_result = convert_validation_result_to_regex(result)
|
|
247
|
+
|
|
248
|
+
# 记录正则校验完成状态
|
|
249
|
+
logger.info(f"[REGEX] 路径正则校验完成: 成功={result.is_valid}, 匹配数={result.match_count}")
|
|
250
|
+
|
|
251
|
+
return RegexValidationResponse(success=True, data=regex_result, error=None)
|
|
252
|
+
|
|
253
|
+
except FileNotFoundError as e:
|
|
254
|
+
return RegexValidationResponse(success=False, data=None, error=str(e))
|
|
255
|
+
except HTTPException as e:
|
|
256
|
+
return RegexValidationResponse(success=False, data=None, error=e.detail)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.error(f"[REGEX] 路径正则校验异常: {str(e)}")
|
|
259
|
+
import traceback
|
|
260
|
+
|
|
261
|
+
traceback.print_exc()
|
|
262
|
+
return RegexValidationResponse(success=False, data=None, error=f"校验过程中发生错误: {str(e)}")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@router.post(
|
|
266
|
+
"/validate/path/batch",
|
|
267
|
+
response_model=dict,
|
|
268
|
+
summary="批量数据校验(Path 模式)",
|
|
269
|
+
# 行为约定:单条校验的失败已被 validate_data_with_path 逐条捕获,整体仍返回 200
|
|
270
|
+
responses={
|
|
271
|
+
200: {
|
|
272
|
+
"description": "批量校验已执行;单条失败不改变整体状态码,失败体现在该条目的 success=false 与 error 字段",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
def validate_batch_with_path(requests: list[ValidationRequest]) -> dict[str, Any]:
|
|
277
|
+
"""
|
|
278
|
+
批量数据校验接口(基于文件路径)。
|
|
279
|
+
|
|
280
|
+
参数:
|
|
281
|
+
requests (list[ValidationRequest]): 校验请求对象列表,每个元素包含文件路径和校验配置
|
|
282
|
+
|
|
283
|
+
返回值:
|
|
284
|
+
dict: 包含 results 键的字典,值为所有单条校验结果的列表
|
|
285
|
+
|
|
286
|
+
业务逻辑:
|
|
287
|
+
1. 遍历传入的请求列表
|
|
288
|
+
2. 对每个请求调用 validate_data_with_path 执行单条路径模式校验
|
|
289
|
+
3. 收集每条请求的校验类型、目标列、成功状态、结果数据和错误信息
|
|
290
|
+
4. 将所有结果组装为统一字典返回
|
|
291
|
+
|
|
292
|
+
错误语义:
|
|
293
|
+
单条请求的失败由 validate_data_with_path 捕获并写入对应条目的
|
|
294
|
+
success=false 与 error 字段,批量接口本身始终返回 200。
|
|
295
|
+
"""
|
|
296
|
+
# 存储所有批量校验结果的列表
|
|
297
|
+
results = []
|
|
298
|
+
# 逐条处理每个校验请求
|
|
299
|
+
for request in requests:
|
|
300
|
+
# 复用路径模式单条校验接口的逻辑
|
|
301
|
+
response = validate_data_with_path(request)
|
|
302
|
+
# 提取关键信息组装为简洁的结果字典
|
|
303
|
+
results.append(
|
|
304
|
+
{
|
|
305
|
+
"validation_type": request.validation_type,
|
|
306
|
+
"target_column": request.target_column_name,
|
|
307
|
+
"success": response.success,
|
|
308
|
+
# 如果校验成功且有数据,将结果对象转为字典;否则为 None
|
|
309
|
+
"result": response.data.model_dump() if response.data else None,
|
|
310
|
+
"error": response.error,
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
# 返回包含所有结果的响应字典
|
|
314
|
+
return {"results": results}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
- 创建并导出数据校验模块的 FastAPI APIRouter 实例
|
|
21
|
+
- 作为数据校验相关路由的注册入口,供上层应用统一挂载
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 使用 FastAPI 的 APIRouter 创建路由实例
|
|
25
|
+
- 设置 tags=["Data Validation"] 便于 Swagger/OpenAPI 文档分类展示
|
|
26
|
+
- prefix 由上层应用通过 include_router 时指定,本模块保持为空
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
from app.api.routers.validation.router import router
|
|
30
|
+
app.include_router(router, prefix="/validation")
|
|
31
|
+
|
|
32
|
+
输出示例:
|
|
33
|
+
无直接输出,提供配置好的 APIRouter 实例
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from fastapi import APIRouter
|
|
39
|
+
|
|
40
|
+
# 创建数据校验模块的路由实例,tags 用于在 API 文档中分组展示
|
|
41
|
+
router = APIRouter(prefix="/api/latest", tags=["Data Validation"])
|
|
42
|
+
|
|
43
|
+
# 导入子模块以注册其路由(content_mode、inline_mode 和 path_mode 通过 from .router import router 装饰本实例)
|
|
44
|
+
from . import content_mode, inline_mode, path_mode # noqa: E402, F401
|
|
45
|
+
|
|
46
|
+
# history 模块使用独立 router(含 prefix),需要 include_router 注册
|
|
47
|
+
from .history import router as history_router # noqa: E402
|
|
48
|
+
|
|
49
|
+
router.include_router(history_router)
|
|
50
|
+
|
|
51
|
+
__all__ = ["router"]
|