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
app/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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 Precis 后端应用包入口"""
|
app/api/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
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 API 模块包入口
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 标记 backend/app/api 目录为 Python 包
|
|
21
|
+
- 作为 API 层的统一入口,供外部导入使用
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 该包包含 FastAPI 应用、依赖注入、中间件、数据模型和路由等子模块
|
|
25
|
+
- 通过 __init__.py 暴露公共接口,简化外部导入路径
|
|
26
|
+
|
|
27
|
+
输入示例:
|
|
28
|
+
from app.api import main
|
|
29
|
+
|
|
30
|
+
输出示例:
|
|
31
|
+
无直接输出,仅作为包标记
|
|
32
|
+
"""
|
app/api/dependencies.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
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 API 依赖注入模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 提供 FastAPI 依赖注入函数
|
|
21
|
+
- 从 HTTP Header 获取并验证项目配置路径
|
|
22
|
+
- 提供 ProjectStore 数据容器在请求生命周期内传递项目路径
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 使用 FastAPI 的 Depends 机制实现依赖注入
|
|
26
|
+
- 通过 X-Project-Config-Path Header 接收项目路径,支持多项目
|
|
27
|
+
- 路径验证包括绝对路径检查和目录存在性检查
|
|
28
|
+
- ProjectStore 可轻松 mock,便于单元测试
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
Header: X-Project-Config-Path: D:/project (项目根,须含 project.precis.yaml)
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
ProjectStore(project_path="D:/project")
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# backend/app/api/dependencies.py
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
from fastapi import Depends, Header, HTTPException
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ProjectStore:
|
|
44
|
+
"""
|
|
45
|
+
项目存储类。
|
|
46
|
+
|
|
47
|
+
这是一个简单的数据容器类,用于在 FastAPI 依赖注入链中传递项目路径信息。
|
|
48
|
+
通过将项目路径封装为对象,可以在类型提示中明确表达依赖关系。
|
|
49
|
+
|
|
50
|
+
设计目的:
|
|
51
|
+
- 提供类型安全的项目路径传递方式
|
|
52
|
+
- 方便在路由处理器中获取项目根目录
|
|
53
|
+
- 支持依赖注入的单元测试(可轻松 mock)
|
|
54
|
+
|
|
55
|
+
属性:
|
|
56
|
+
project_path: 项目配置目录的绝对路径
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, project_path: str):
|
|
60
|
+
"""
|
|
61
|
+
初始化项目存储对象。
|
|
62
|
+
|
|
63
|
+
:param project_path: 项目配置目录的绝对路径
|
|
64
|
+
"""
|
|
65
|
+
self.project_path = project_path
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def get_project_config_path(
|
|
69
|
+
x_project_config_path: str = Header(..., description="项目根目录的绝对路径(须含 project.precis.yaml)"),
|
|
70
|
+
) -> str:
|
|
71
|
+
"""
|
|
72
|
+
获取并验证项目配置路径的依赖函数。
|
|
73
|
+
|
|
74
|
+
该函数是 FastAPI 的依赖项,用于从 HTTP Header 中提取项目配置路径,
|
|
75
|
+
并进行合法性验证:
|
|
76
|
+
1. 检查路径是否为绝对路径
|
|
77
|
+
2. 检查路径对应的目录是否存在
|
|
78
|
+
3. 必须含 project.precis.yaml(合法 Precis 项目根标识)
|
|
79
|
+
|
|
80
|
+
B-sec3 安全加固: 原校验仅"绝对+是目录",任意目录都能当项目根,导致后续
|
|
81
|
+
manifest/数据源/AI 配置写入可能落到任意目录。现对齐 AI 路由的
|
|
82
|
+
validate_project_path,要求 manifest 存在。项目首次创建走独立的
|
|
83
|
+
/projects/create 端点(不经此依赖),故不影响创建流程。
|
|
84
|
+
|
|
85
|
+
处理流程:
|
|
86
|
+
1. 从 X-Project-Config-Path Header 获取路径
|
|
87
|
+
2. 委托 _validate_project_root 做完整校验(绝对路径/拒绝 `..`/目录存在/manifest 存在)
|
|
88
|
+
3. 返回验证后的绝对路径
|
|
89
|
+
|
|
90
|
+
:param x_project_config_path: HTTP Header 中的项目配置路径
|
|
91
|
+
:return: 验证通过的项目配置绝对路径
|
|
92
|
+
:raises HTTPException: 路径验证失败时返回 400 或 404 错误
|
|
93
|
+
|
|
94
|
+
Header 示例(项目根目录语义,不是 .precis/ 子目录):
|
|
95
|
+
X-Project-Config-Path: /path/to/project
|
|
96
|
+
"""
|
|
97
|
+
return _validate_project_root(x_project_config_path)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _validate_project_root(raw_path: str | None) -> str:
|
|
101
|
+
"""校验路径是否为合法 Precis 项目根(单一事实源)。
|
|
102
|
+
|
|
103
|
+
B-sec3: 统一 get_project_config_path 与 ai/utils.validate_project_path 的校验逻辑,
|
|
104
|
+
消除双套不一致(原 ai 路径要求 manifest,通用依赖不要求,弱者为默认)。
|
|
105
|
+
|
|
106
|
+
要求:
|
|
107
|
+
- 非空
|
|
108
|
+
- 必须是绝对路径(Windows 下 abspath 会把相对路径转绝对,故须在规范化前先判)
|
|
109
|
+
- 不含 ".."(normpath 后仍不含)
|
|
110
|
+
- 必须是存在的目录
|
|
111
|
+
- 必须含 project.precis.yaml(合法项目根标识)
|
|
112
|
+
|
|
113
|
+
参数:
|
|
114
|
+
raw_path: 原始路径字符串(通常来自 Header)
|
|
115
|
+
|
|
116
|
+
返回:
|
|
117
|
+
规范化后的绝对路径
|
|
118
|
+
|
|
119
|
+
抛出:
|
|
120
|
+
HTTPException(400): 校验失败
|
|
121
|
+
"""
|
|
122
|
+
if not raw_path:
|
|
123
|
+
raise HTTPException(status_code=400, detail="X-Project-Config-Path header 不能为空。")
|
|
124
|
+
# 步骤1:先校验原始输入是否为绝对路径
|
|
125
|
+
# 在 Windows 下,os.path.abspath 会把相对路径(如 "../project")解析成绝对路径,
|
|
126
|
+
# 导致后续 isabs 检查无法拒绝相对路径。因此必须在规范化之前先做判断。
|
|
127
|
+
if not os.path.isabs(raw_path):
|
|
128
|
+
raise HTTPException(status_code=400, detail="X-Project-Config-Path header 必须是一个绝对路径。")
|
|
129
|
+
|
|
130
|
+
# 路径标准化并解析为绝对路径,防御 Path Traversal
|
|
131
|
+
normalized_path = os.path.abspath(os.path.normpath(raw_path))
|
|
132
|
+
# 拒绝路径穿越(normpath 后仍出现 .. 说明超出根)
|
|
133
|
+
if ".." in normalized_path.split(os.sep):
|
|
134
|
+
raise HTTPException(status_code=400, detail="X-Project-Config-Path 不允许包含 .. 目录穿越。")
|
|
135
|
+
|
|
136
|
+
# 步骤2:验证路径对应的目录是否存在
|
|
137
|
+
if not os.path.isdir(normalized_path):
|
|
138
|
+
raise HTTPException(status_code=404, detail=f"提供的项目配置路径不存在: {normalized_path}")
|
|
139
|
+
|
|
140
|
+
# 步骤3:验证是合法 Precis 项目根(含 manifest)
|
|
141
|
+
manifest = os.path.join(normalized_path, "project.precis.yaml")
|
|
142
|
+
if not os.path.isfile(manifest):
|
|
143
|
+
# §2.1: manifest 缺失属"资源不存在"语义,统一 404(原 400 与 GET /manifest
|
|
144
|
+
# 端点对同一情形不同状态码,且前端 404 自愈链永不触发)。detail 携带结构化
|
|
145
|
+
# 错误码,前端按 detail.code 识别(不再匹配中文措辞前缀)。
|
|
146
|
+
raise HTTPException(
|
|
147
|
+
status_code=404,
|
|
148
|
+
detail={
|
|
149
|
+
"code": "PROJECT_NOT_FOUND",
|
|
150
|
+
"message": (
|
|
151
|
+
"项目路径下未找到 project.precis.yaml(非合法 Precis 项目根)。"
|
|
152
|
+
"若要初始化新项目,请使用 POST /api/latest/projects/create。"
|
|
153
|
+
),
|
|
154
|
+
"path": normalized_path,
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return normalized_path
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def get_project_store(project_path: str = Depends(get_project_config_path)) -> ProjectStore:
|
|
162
|
+
"""
|
|
163
|
+
创建项目存储对象的依赖函数。
|
|
164
|
+
|
|
165
|
+
该函数是 FastAPI 的依赖项,负责:
|
|
166
|
+
1. 调用 get_project_config_path 获取验证后的项目路径
|
|
167
|
+
2. 创建 ProjectStore 实例并返回
|
|
168
|
+
|
|
169
|
+
这种分层设计的好处:
|
|
170
|
+
- 职责分离:路径验证和对象创建分别处理
|
|
171
|
+
- 可复用性:get_project_config_path 可以被其他依赖函数复用
|
|
172
|
+
- 可测试性:每个函数都可以独立进行单元测试
|
|
173
|
+
|
|
174
|
+
:param project_path: 已验证的项目配置路径(由 get_project_config_path 注入)
|
|
175
|
+
:return: 包含项目路径的 ProjectStore 实例
|
|
176
|
+
|
|
177
|
+
使用示例:
|
|
178
|
+
@app.get("/projects/{project_id}/schemas")
|
|
179
|
+
async def get_schemas(project: ProjectStore = Depends(get_project_store)):
|
|
180
|
+
# 可以直接使用 project.project_path 访问项目路径
|
|
181
|
+
schemas = load_schemas(project.project_path)
|
|
182
|
+
return schemas
|
|
183
|
+
"""
|
|
184
|
+
# 创建并返回项目存储对象,将路径封装为对象形式
|
|
185
|
+
return ProjectStore(project_path=project_path)
|
app/api/main.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/api/main.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview FastAPI 应用入口模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 创建和配置 FastAPI 应用实例
|
|
22
|
+
- 配置 CORS 中间件支持跨域请求
|
|
23
|
+
- 注册所有 API 路由
|
|
24
|
+
- 提供根路径的健康检查端点
|
|
25
|
+
|
|
26
|
+
架构设计:
|
|
27
|
+
- 单入口: 所有路由通过此模块注册
|
|
28
|
+
- 中间件模式: 使用 FastAPI 中间件机制处理横切关注点
|
|
29
|
+
- 模块化路由: 路由按功能模块分组(project, utils, reporting 等)
|
|
30
|
+
|
|
31
|
+
Electron 集成说明:
|
|
32
|
+
- 打包模式:主进程每次启动后端生成随机 token,经 PRECIS_API_TOKEN 注入本进程,
|
|
33
|
+
TokenOriginAuthMiddleware 对携带有效 token(X-Precis-Auth 头)的请求放行 null
|
|
34
|
+
Origin 的 CORS(预检代答 + 响应头补写);无效/缺失 token 的 null Origin 仍被
|
|
35
|
+
DynamicPortCORSMiddleware 拒绝(防沙箱 iframe 跨域读取本机 API)
|
|
36
|
+
- DynamicPortCORSMiddleware 放行 Electron 自定义协议(app:// / electron://),
|
|
37
|
+
支持 127.0.0.1 / localhost 的任意端口(动态端口分配)
|
|
38
|
+
- 桌面应用经内嵌窗口加载前端,无跨站风险
|
|
39
|
+
- PRECIS_ALLOW_NULL_ORIGIN=1 为旧的全局放行兼容开关(DynamicPortCORSMiddleware
|
|
40
|
+
内保留读取逻辑),打包模式已改用 token 机制、不再注入该变量
|
|
41
|
+
|
|
42
|
+
日志配置:
|
|
43
|
+
- 配置 logging 以显示 HTTP 请求和响应信息
|
|
44
|
+
- 使用 uvicorn 的访问日志记录器
|
|
45
|
+
- 可通过环境变量 LOG_LEVEL 控制日志级别
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
import logging
|
|
49
|
+
import os
|
|
50
|
+
import re
|
|
51
|
+
import sys
|
|
52
|
+
|
|
53
|
+
# ============================================================================
|
|
54
|
+
# 日志与输出配置
|
|
55
|
+
# ============================================================================
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
logger.info("[INIT] Precis Backend 启动中...")
|
|
59
|
+
logger.info("[INIT] Python 版本: %s", sys.version)
|
|
60
|
+
|
|
61
|
+
from fastapi import FastAPI
|
|
62
|
+
from fastapi.exceptions import RequestValidationError
|
|
63
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
64
|
+
|
|
65
|
+
from .middleware.exception_handler import (
|
|
66
|
+
ExceptionHandlerMiddleware,
|
|
67
|
+
request_validation_exception_handler,
|
|
68
|
+
)
|
|
69
|
+
from .middleware.request_logging import RequestLoggingMiddleware
|
|
70
|
+
from .middleware.token_auth import TokenOriginAuthMiddleware
|
|
71
|
+
from .routers import (
|
|
72
|
+
ai_router,
|
|
73
|
+
connection_rules_router,
|
|
74
|
+
data_sources_router,
|
|
75
|
+
files_router,
|
|
76
|
+
preview_router,
|
|
77
|
+
project_router,
|
|
78
|
+
projects_router,
|
|
79
|
+
regex_router,
|
|
80
|
+
reporting_router,
|
|
81
|
+
validation_router,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# ============================================================================
|
|
85
|
+
# FastAPI 应用创建
|
|
86
|
+
# ============================================================================
|
|
87
|
+
|
|
88
|
+
# 创建 FastAPI 应用实例
|
|
89
|
+
# [FastAPI 配置说明]
|
|
90
|
+
# - title: API 文档中显示的标题
|
|
91
|
+
# - description: API 功能描述
|
|
92
|
+
# - version: API 版本号,用于版本管理
|
|
93
|
+
app = FastAPI(
|
|
94
|
+
title="数据校验工具 API",
|
|
95
|
+
description="为数据校验工具前端提供后端服务的 API。",
|
|
96
|
+
version="1.0.0",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# 初始化应用状态(Web 模式使用)
|
|
100
|
+
app.state.current_project_path = None
|
|
101
|
+
app.state.current_project_name = None
|
|
102
|
+
|
|
103
|
+
# 端口文件自愈:uvicorn --reload 重启 app 进程时(或外部进程误删端口文件后),
|
|
104
|
+
# 依据 start_server 通过环境变量传入的实际端口重写 backend/.backend-port,
|
|
105
|
+
# 保证 Vite 代理 / Electron 主进程始终能发现后端,避免 502。
|
|
106
|
+
_actual_port_env = os.environ.get("PRECIS_BACKEND_PORT_ACTUAL")
|
|
107
|
+
if _actual_port_env and _actual_port_env.isdigit():
|
|
108
|
+
from app.shared.core.config.server import write_port_file
|
|
109
|
+
|
|
110
|
+
write_port_file(int(_actual_port_env))
|
|
111
|
+
|
|
112
|
+
# ============================================================================
|
|
113
|
+
# 日志配置
|
|
114
|
+
# ============================================================================
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def configure_logging() -> None:
|
|
118
|
+
"""配置 logging 以显示 HTTP 请求和响应信息。"""
|
|
119
|
+
log_level = os.environ.get("LOG_LEVEL", "INFO").upper()
|
|
120
|
+
uvicorn_logger = logging.getLogger("uvicorn")
|
|
121
|
+
uvicorn_logger.setLevel(log_level)
|
|
122
|
+
|
|
123
|
+
# 减少 FastAPI reload 模式的日志噪音
|
|
124
|
+
if os.environ.get("UVICORN_RELOAD", "").lower() in ("true", "1"):
|
|
125
|
+
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
|
|
126
|
+
|
|
127
|
+
logger.info("[CONFIG] 日志级别: %s", log_level)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
configure_logging()
|
|
131
|
+
|
|
132
|
+
# ============================================================================
|
|
133
|
+
# CORS 中间件配置
|
|
134
|
+
# ============================================================================
|
|
135
|
+
|
|
136
|
+
# 自定义 CORS 中间件,支持动态端口匹配
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class DynamicPortCORSMiddleware(CORSMiddleware):
|
|
140
|
+
"""
|
|
141
|
+
支持动态端口与桌面应用协议的 CORS 中间件
|
|
142
|
+
|
|
143
|
+
功能:
|
|
144
|
+
- 允许 127.0.0.1 和 localhost 的任意端口访问(支持动态端口分配)
|
|
145
|
+
- 放行 Electron 自定义协议(app:// / electron://)下浏览器发送的 null/空 Origin
|
|
146
|
+
- 保留原有 CORS 中间件的所有功能
|
|
147
|
+
|
|
148
|
+
[安全考量]
|
|
149
|
+
- null/空 Origin 默认拒绝:后端无鉴权且存在文件读取端点,沙箱 iframe / file:// 页面
|
|
150
|
+
发送的 null Origin 一旦放行,任意网页可跨域读取本机 API 响应(loopback 端口可被
|
|
151
|
+
浏览器枚举)。打包模式改由 TokenOriginAuthMiddleware 按"一次性 token"放行本应用
|
|
152
|
+
页面(携带有效 X-Precis-Auth 头的请求在其上游补写 CORS 头/代答预检),本中间件
|
|
153
|
+
不再对 null Origin 无差别放行;PRECIS_ALLOW_NULL_ORIGIN=1 保留为兼容开关
|
|
154
|
+
(Electron 旧版主进程注入时仍生效),打包模式不再注入。
|
|
155
|
+
- 若未来该 API 暴露到网络(非 127.0.0.1),必须进一步收紧(token 鉴权 + Host 校验)。
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def is_allowed_origin(self, origin: str) -> bool:
|
|
159
|
+
"""
|
|
160
|
+
@methoddesc 判断请求 Origin 是否被允许
|
|
161
|
+
|
|
162
|
+
业务用途:
|
|
163
|
+
- 在 CORS 握手阶段决定是否在响应中回写 Access-Control-Allow-Origin
|
|
164
|
+
- 在标准 allow_origins 之外,额外放行桌面应用场景下的特殊 Origin
|
|
165
|
+
|
|
166
|
+
参数:
|
|
167
|
+
origin: 浏览器请求头中的 Origin 字段(形如 ``http://127.0.0.1:5173``、
|
|
168
|
+
``app://.`` 或 ``null``)
|
|
169
|
+
|
|
170
|
+
返回:
|
|
171
|
+
True 表示允许该 Origin 的跨域请求,False 表示拒绝
|
|
172
|
+
"""
|
|
173
|
+
# Electron 打包模式已改用一次性 token 机制(TokenOriginAuthMiddleware 对携带
|
|
174
|
+
# 有效 X-Precis-Auth 头的请求补写 CORS 头)。此处保留 PRECIS_ALLOW_NULL_ORIGIN
|
|
175
|
+
# 兼容开关:旧版 Electron 主进程注入该变量时仍对 null/空 Origin 全局放行。
|
|
176
|
+
# 默认拒绝:null Origin 也来自沙箱 iframe 与 file:// 页面——后端无鉴权且存在
|
|
177
|
+
# 文件读取端点,放行 null 等于允许任意网页经沙箱 iframe 跨域读取本机 API 响应。
|
|
178
|
+
if (origin == "null" or origin == "") and os.environ.get("PRECIS_ALLOW_NULL_ORIGIN", "").strip().lower() in (
|
|
179
|
+
"1",
|
|
180
|
+
"true",
|
|
181
|
+
"yes",
|
|
182
|
+
):
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
# 检查是否在明确允许列表中
|
|
186
|
+
if origin in self.allow_origins:
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
# 支持 127.0.0.1 和 localhost 的任意端口(动态端口分配场景)
|
|
190
|
+
# 匹配 http://127.0.0.1:PORT 或 http://localhost:PORT 格式
|
|
191
|
+
if re.match(r"^http://(127\.0\.0\.1|localhost):\d+$", origin):
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# 定义允许的跨域来源列表
|
|
198
|
+
# [设计说明]
|
|
199
|
+
# - 明确列出允许的来源,而非使用通配符,便于审计
|
|
200
|
+
# - 127.0.0.1 / localhost 的任意端口及 null/空 Origin 由中间件逻辑放行(见上)
|
|
201
|
+
# - [安全考量] 生产环境应限制为具体域名,避免开放过多来源
|
|
202
|
+
origins = [
|
|
203
|
+
# 后端自检(动态端口范围)
|
|
204
|
+
"http://127.0.0.1:8000",
|
|
205
|
+
# macOS Electron 应用协议(前端打包后以 app://./index.html 加载)
|
|
206
|
+
"app://.",
|
|
207
|
+
# Electron 通用协议
|
|
208
|
+
"electron://.",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# 422 请求校验错误全局 handler:detail 结构保持 FastAPI 默认,msg 本地化为中文
|
|
212
|
+
app.add_exception_handler(RequestValidationError, request_validation_exception_handler)
|
|
213
|
+
|
|
214
|
+
app.add_middleware(
|
|
215
|
+
DynamicPortCORSMiddleware,
|
|
216
|
+
allow_origins=origins,
|
|
217
|
+
allow_credentials=True,
|
|
218
|
+
allow_methods=["*"],
|
|
219
|
+
allow_headers=["*"],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# 一次性 token 放行中间件(打包模式):
|
|
223
|
+
# [中间件顺序] FastAPI/Starlette 的 add_middleware 后添加的先执行(外层)。
|
|
224
|
+
# 本中间件在 CORSMiddleware 之后添加 → 位于 CORS 外层、先于 CORS 处理请求:
|
|
225
|
+
# - 携带有效 token(X-Precis-Auth)的 null Origin 预检在此被直接代答放行,
|
|
226
|
+
# 不会被 CORSMiddleware 以"Origin 不在允许列表"拒绝
|
|
227
|
+
# - 无效/缺失 token 的请求原样透传给 CORSMiddleware,null Origin 仍按既有规则拒绝
|
|
228
|
+
# - 未配置 PRECIS_API_TOKEN(Web/开发模式)时完全直通,既有 localhost CORS 行为不变
|
|
229
|
+
app.add_middleware(TokenOriginAuthMiddleware)
|
|
230
|
+
|
|
231
|
+
app.add_middleware(ExceptionHandlerMiddleware)
|
|
232
|
+
app.add_middleware(RequestLoggingMiddleware)
|
|
233
|
+
|
|
234
|
+
# ============================================================================
|
|
235
|
+
# 路由注册
|
|
236
|
+
# ============================================================================
|
|
237
|
+
|
|
238
|
+
# 注册各功能模块的路由
|
|
239
|
+
# [FastAPI] include_router 将路由添加到应用
|
|
240
|
+
# 路由顺序不影响匹配,FastAPI 使用最长前缀匹配
|
|
241
|
+
# 【路由列表】按功能模块分组,便于维护和扩展
|
|
242
|
+
app.include_router(project_router) # 项目管理路由(V2 配置读写)
|
|
243
|
+
app.include_router(projects_router) # 项目扫描路由(Web 模式)
|
|
244
|
+
app.include_router(files_router) # 文件操作路由(Web 模式)
|
|
245
|
+
app.include_router(ai_router) # AI 辅助路由(智能提示、生成)
|
|
246
|
+
app.include_router(regex_router) # 正则表达式路由(测试、验证)
|
|
247
|
+
app.include_router(reporting_router) # 报告路由(校验结果导出)
|
|
248
|
+
app.include_router(preview_router) # 预览路由(数据预览、Schema 预览)
|
|
249
|
+
app.include_router(data_sources_router) # 数据源路由(文件上传、加载)
|
|
250
|
+
app.include_router(validation_router) # 校验路由(执行校验、获取结果)
|
|
251
|
+
app.include_router(connection_rules_router) # 连接规则路由(画布连线规则)
|
|
252
|
+
|
|
253
|
+
# ============================================================================
|
|
254
|
+
# 根路径路由
|
|
255
|
+
# ============================================================================
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@app.get("/")
|
|
259
|
+
async def root() -> dict[str, str]:
|
|
260
|
+
"""
|
|
261
|
+
@methoddesc 根路径路由,返回简单的欢迎信息
|
|
262
|
+
|
|
263
|
+
业务用途:
|
|
264
|
+
- 提供 API 根路径访问时的友好提示
|
|
265
|
+
- 不包含敏感信息,适合公开访问
|
|
266
|
+
"""
|
|
267
|
+
return {"message": "欢迎使用数据校验工具 API! 请访问 /docs 查看详情。"}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@app.get("/health")
|
|
271
|
+
async def health() -> dict[str, str]:
|
|
272
|
+
"""
|
|
273
|
+
@methoddesc 健康检查端点
|
|
274
|
+
|
|
275
|
+
业务用途:
|
|
276
|
+
- 供 Electron 主进程和负载均衡器探测后端存活状态
|
|
277
|
+
- 返回 {"status": "ok"} 即表示 FastAPI 应用已启动并能响应 HTTP 请求
|
|
278
|
+
|
|
279
|
+
@returns dict - 包含 status 字段的响应对象
|
|
280
|
+
"""
|
|
281
|
+
return {"status": "ok"}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@app.get("/api/latest/version", summary="获取应用版本号")
|
|
285
|
+
def get_version() -> dict[str, str]:
|
|
286
|
+
"""
|
|
287
|
+
@methoddesc 返回当前应用版本号
|
|
288
|
+
|
|
289
|
+
业务用途:
|
|
290
|
+
- Web 模式下替代 Electron 的 getAppVersion IPC
|
|
291
|
+
- 版本优先级:PRECIS_APP_VERSION 环境变量(Electron 主进程注入 app.getVersion())→
|
|
292
|
+
包元数据(开发环境 pip install -e 提供)→ "0.0.0-dev"
|
|
293
|
+
(打包环境不安装 precis 包元数据,importlib.metadata 拿不到真实版本,
|
|
294
|
+
环境变量是桌面打包场景的唯一真实来源)
|
|
295
|
+
|
|
296
|
+
@returns dict - 包含 version 字段的响应对象
|
|
297
|
+
"""
|
|
298
|
+
from app.shared.core.app_version import get_app_version
|
|
299
|
+
|
|
300
|
+
ver = get_app_version(fallback="0.0.0-dev")
|
|
301
|
+
return {"version": ver}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 API 中间件模块包入口
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 标记 backend/app/api/middleware 目录为 Python 包
|
|
21
|
+
- 聚合全局异常处理和请求日志记录等中间件组件
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 各中间件继承 Starlette BaseHTTPMiddleware
|
|
25
|
+
- 通过主应用 main.py 统一注册,按顺序执行
|
|
26
|
+
- 异常处理中间件应在最外层,请求日志在最内层
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
from app.api.middleware.exception_handler import ExceptionHandlerMiddleware
|
|
30
|
+
from app.api.middleware.request_logging import RequestLoggingMiddleware
|
|
31
|
+
|
|
32
|
+
输出示例:
|
|
33
|
+
无直接输出,仅作为包标记
|
|
34
|
+
"""
|