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,222 @@
|
|
|
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 校验报告导出模块(P2-3 --report)
|
|
17
|
+
|
|
18
|
+
功能概述:
|
|
19
|
+
- 把 validate 的 JSON 契约 payload 导出为人类可分享的报告文件
|
|
20
|
+
- HTML:自包含单文件(内联 CSS,无外部资源),违规明细表
|
|
21
|
+
- Excel:openpyxl(backend 现有依赖),每张表一个 sheet,违规行标红
|
|
22
|
+
|
|
23
|
+
设计说明:
|
|
24
|
+
- 输入直接复用 `_build_json_payload` 的契约结构,保证
|
|
25
|
+
"stdout JSON / 报告文件" 两产物内容一致(验收口径)
|
|
26
|
+
- 纯函数 + 显式路径参数,无隐藏 I/O;HTML 值经 html.escape 防注入
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import html
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
# 违规明细表的列头(HTML 与 Excel 共用口径)
|
|
36
|
+
_REPORT_COLUMNS = ("表", "列", "行号", "值", "约束类型", "约束文件", "错误消息")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _error_row(entry: dict[str, Any]) -> list[str]:
|
|
40
|
+
"""把契约错误条目转为报告行(None 统一展示为空串)。"""
|
|
41
|
+
message = entry.get("error_message") or ""
|
|
42
|
+
suggestion = entry.get("suggestion")
|
|
43
|
+
if suggestion:
|
|
44
|
+
# 修复建议拼进消息单元格,避免两种导出格式各加一列
|
|
45
|
+
message = f"{message}(建议:{suggestion})"
|
|
46
|
+
return [
|
|
47
|
+
entry.get("table") or "",
|
|
48
|
+
entry.get("column") or "",
|
|
49
|
+
"" if entry.get("row_index") is None else str(entry["row_index"]),
|
|
50
|
+
"" if entry.get("cell_value") is None else str(entry["cell_value"]),
|
|
51
|
+
entry.get("constraint_type") or "",
|
|
52
|
+
entry.get("constraint_file") or "",
|
|
53
|
+
message,
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _summary_line(payload: dict[str, Any]) -> str:
|
|
58
|
+
"""一句话概览:结论 + 检查数 + 耗时。"""
|
|
59
|
+
summary = payload.get("summary", {})
|
|
60
|
+
verdict = "通过" if payload.get("is_valid") else "发现违规"
|
|
61
|
+
return (
|
|
62
|
+
f"校验{verdict}:约束检查 {summary.get('constraints_total', 0)} 项,"
|
|
63
|
+
f"通过 {summary.get('constraints_passed', 0)} / 失败 {summary.get('constraints_failed', 0)},"
|
|
64
|
+
f"耗时 {payload.get('duration_ms', 0)} ms"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def export_html_report(payload: dict[str, Any], output_path: str | Path) -> Path:
|
|
69
|
+
"""导出自包含 HTML 报告。
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
payload: validate JSON 契约 payload(_build_json_payload 输出)
|
|
73
|
+
output_path: 报告文件路径(.html)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
写入的文件路径
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
OSError: 写文件失败
|
|
80
|
+
"""
|
|
81
|
+
rows = payload.get("errors", [])
|
|
82
|
+
table_headers = "".join(f"<th>{html.escape(col)}</th>" for col in _REPORT_COLUMNS)
|
|
83
|
+
table_rows: list[str] = []
|
|
84
|
+
for index, entry in enumerate(rows, 1):
|
|
85
|
+
cells = "".join(f"<td>{html.escape(cell)}</td>" for cell in _error_row(entry))
|
|
86
|
+
row_class = "fail" if not payload.get("is_valid") else ""
|
|
87
|
+
table_rows.append(f'<tr class="{row_class}"><td class="idx">{index}</td>{cells}</tr>')
|
|
88
|
+
table_html = "".join(table_rows) if table_rows else '<tr><td colspan="8" class="empty">无违规记录</td></tr>'
|
|
89
|
+
|
|
90
|
+
tables_info = (
|
|
91
|
+
"、".join(
|
|
92
|
+
f"{t.get('name') or '?'}({t.get('rows') if t.get('rows') is not None else '?'} 行)"
|
|
93
|
+
for t in payload.get("tables", [])
|
|
94
|
+
)
|
|
95
|
+
or "无"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
document = f"""<!DOCTYPE html>
|
|
99
|
+
<html lang="zh-CN">
|
|
100
|
+
<head>
|
|
101
|
+
<meta charset="utf-8">
|
|
102
|
+
<title>Precis 校验报告</title>
|
|
103
|
+
<style>
|
|
104
|
+
body {{ font-family: "Segoe UI", "Microsoft YaHei", sans-serif; margin: 2rem; color: #1f2937; }}
|
|
105
|
+
h1 {{ font-size: 1.4rem; }}
|
|
106
|
+
.meta {{ color: #6b7280; margin-bottom: 1rem; }}
|
|
107
|
+
.verdict {{ display: inline-block; padding: 0.2rem 0.8rem; border-radius: 999px; font-weight: 600; }}
|
|
108
|
+
.verdict.pass {{ background: #d1fae5; color: #065f46; }}
|
|
109
|
+
.verdict.fail {{ background: #fee2e2; color: #991b1b; }}
|
|
110
|
+
table {{ border-collapse: collapse; width: 100%; font-size: 0.9rem; }}
|
|
111
|
+
th, td {{ border: 1px solid #d1d5db; padding: 0.4rem 0.6rem; text-align: left; vertical-align: top; }}
|
|
112
|
+
th {{ background: #f3f4f6; }}
|
|
113
|
+
tr.fail td {{ background: #fef2f2; }}
|
|
114
|
+
td.idx {{ color: #9ca3af; width: 2.5rem; }}
|
|
115
|
+
td.empty {{ text-align: center; color: #6b7280; padding: 2rem; }}
|
|
116
|
+
</style>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<h1>Precis 数据校验报告</h1>
|
|
120
|
+
<p class="meta">
|
|
121
|
+
<span class="verdict {"pass" if payload.get("is_valid") else "fail"}">{"通过" if payload.get("is_valid") else "发现违规"}</span>
|
|
122
|
+
{_summary_line(payload)}
|
|
123
|
+
<br>数据表:{html.escape(tables_info)}
|
|
124
|
+
</p>
|
|
125
|
+
<table>
|
|
126
|
+
<thead><tr><th>#</th>{table_headers}</tr></thead>
|
|
127
|
+
<tbody>{table_html}</tbody>
|
|
128
|
+
</table>
|
|
129
|
+
</body>
|
|
130
|
+
</html>
|
|
131
|
+
"""
|
|
132
|
+
path = Path(output_path)
|
|
133
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
134
|
+
path.write_text(document, encoding="utf-8")
|
|
135
|
+
return path
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def export_excel_report(payload: dict[str, Any], output_path: str | Path) -> Path:
|
|
139
|
+
"""导出 Excel 报告(每张表一个 sheet,违规行标红)。
|
|
140
|
+
|
|
141
|
+
Sheet 组织:每张数据表一个 sheet(列出该表违规明细);无表归属的错误
|
|
142
|
+
进入"汇总" sheet;另有"概览" sheet 记录结论与统计。
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
payload: validate JSON 契约 payload(_build_json_payload 输出)
|
|
146
|
+
output_path: 报告文件路径(.xlsx)
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
写入的文件路径
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
OSError: 写文件失败
|
|
153
|
+
"""
|
|
154
|
+
from openpyxl import Workbook
|
|
155
|
+
from openpyxl.styles import Alignment, Font, PatternFill
|
|
156
|
+
|
|
157
|
+
red_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid")
|
|
158
|
+
header_font = Font(bold=True)
|
|
159
|
+
|
|
160
|
+
workbook = Workbook()
|
|
161
|
+
# 概览 sheet
|
|
162
|
+
overview = workbook.active
|
|
163
|
+
overview.title = "概览"
|
|
164
|
+
overview.append(["结论", "通过" if payload.get("is_valid") else "发现违规"])
|
|
165
|
+
for key, label in (
|
|
166
|
+
("constraints_total", "约束检查总数"),
|
|
167
|
+
("constraints_passed", "通过数"),
|
|
168
|
+
("constraints_failed", "失败数"),
|
|
169
|
+
):
|
|
170
|
+
overview.append([label, payload.get("summary", {}).get(key, 0)])
|
|
171
|
+
overview.append(["耗时(ms)", payload.get("duration_ms", 0)])
|
|
172
|
+
overview.append(["违规条数", len(payload.get("errors", []))])
|
|
173
|
+
overview.append(["数据表", ", ".join(str(t.get("name")) for t in payload.get("tables", []))])
|
|
174
|
+
for cell in overview[1]:
|
|
175
|
+
cell.font = header_font
|
|
176
|
+
|
|
177
|
+
# 按表分组违规条目(table 为空/None 的归入"汇总" sheet)
|
|
178
|
+
errors_by_table: dict[str, list[dict[str, Any]]] = {}
|
|
179
|
+
for entry in payload.get("errors", []):
|
|
180
|
+
key = entry.get("table") or "汇总"
|
|
181
|
+
errors_by_table.setdefault(key, []).append(entry)
|
|
182
|
+
|
|
183
|
+
sheet_names = {t.get("name") for t in payload.get("tables", []) if t.get("name")}
|
|
184
|
+
for name in sorted(sheet_names | set(errors_by_table.keys())):
|
|
185
|
+
# Excel sheet 名长度上限 31 字符,超长截断
|
|
186
|
+
sheet = workbook.create_sheet(title=name[:31])
|
|
187
|
+
sheet.append(list(_REPORT_COLUMNS))
|
|
188
|
+
for cell in sheet[1]:
|
|
189
|
+
cell.font = header_font
|
|
190
|
+
cell.alignment = Alignment(horizontal="center")
|
|
191
|
+
for entry in errors_by_table.get(name, []):
|
|
192
|
+
sheet.append(_error_row(entry))
|
|
193
|
+
for cell in sheet[sheet.max_row]:
|
|
194
|
+
cell.fill = red_fill
|
|
195
|
+
if not errors_by_table.get(name):
|
|
196
|
+
sheet.append(["(该表无违规)"])
|
|
197
|
+
|
|
198
|
+
path = Path(output_path)
|
|
199
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
workbook.save(path)
|
|
201
|
+
return path
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def export_report(payload: dict[str, Any], output_path: str | Path) -> Path:
|
|
205
|
+
"""按扩展名分派报告导出(.html/.htm → HTML;.xlsx → Excel)。
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
payload: validate JSON 契约 payload
|
|
209
|
+
output_path: 报告文件路径
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
写入的文件路径
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
ValueError: 扩展名不支持
|
|
216
|
+
"""
|
|
217
|
+
suffix = Path(output_path).suffix.lower()
|
|
218
|
+
if suffix in (".html", ".htm"):
|
|
219
|
+
return export_html_report(payload, output_path)
|
|
220
|
+
if suffix == ".xlsx":
|
|
221
|
+
return export_excel_report(payload, output_path)
|
|
222
|
+
raise ValueError(f"不支持的报告格式: {suffix}(支持 .html / .xlsx)")
|
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
- 解析 manifest 中配置的第一个数据源目录(绝对/相对路径)
|
|
21
|
+
- 根据 Schema source 定义解析文件路径
|
|
22
|
+
- 自动发现匹配表名的数据文件(按扩展名优先级搜索)
|
|
23
|
+
- 支持 Excel 多 sheet 场景的路径解析
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 单一职责模式: 只做路径解析,不涉及数据加载或校验
|
|
27
|
+
- 从 ValidationExecutor 中提取,独立为可复用组件
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
resolver = DataSourceResolver(project_root, manifest, schema_by_id)
|
|
31
|
+
data_dir = resolver.resolve_first_data_source()
|
|
32
|
+
file_path, sheet = resolver.resolve_source_path(data_dir, schema_file)
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
data_dir = "/path/to/data"
|
|
36
|
+
file_path = "/path/to/data/users.xlsx"
|
|
37
|
+
sheet = "Sheet1"
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import logging
|
|
41
|
+
import os
|
|
42
|
+
|
|
43
|
+
from app.shared.core.project.manifest.types import ProjectManifestV2
|
|
44
|
+
from app.shared.core.project.schema.types import TableSchemaFile
|
|
45
|
+
from app.shared.core.utils.path_utils import normalize_to_posix
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DataSourceResolver:
|
|
51
|
+
"""
|
|
52
|
+
@classdesc 数据源路径解析器
|
|
53
|
+
|
|
54
|
+
负责:
|
|
55
|
+
- 解析 manifest 中配置的第一个数据源目录
|
|
56
|
+
- 根据 Schema source 定义解析文件路径(绝对/相对)
|
|
57
|
+
- 自动发现匹配表名的数据文件(按扩展名优先级搜索)
|
|
58
|
+
|
|
59
|
+
设计决策:
|
|
60
|
+
- 从 ValidationExecutor 中提取,单一职责:只做路径解析,不涉及数据加载或校验
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
project_root: str,
|
|
66
|
+
manifest: ProjectManifestV2,
|
|
67
|
+
schema_by_id: dict[str, TableSchemaFile],
|
|
68
|
+
):
|
|
69
|
+
self.project_root = project_root
|
|
70
|
+
self.manifest = manifest
|
|
71
|
+
self._schema_by_id = schema_by_id
|
|
72
|
+
|
|
73
|
+
def resolve_first_data_source(self) -> str | None:
|
|
74
|
+
"""
|
|
75
|
+
@methoddesc 解析 manifest 中配置的第一个数据源目录
|
|
76
|
+
|
|
77
|
+
返回第一个数据源对应的绝对路径。支持绝对路径和相对路径两种模式。
|
|
78
|
+
|
|
79
|
+
返回:
|
|
80
|
+
数据源目录的绝对路径,未配置或不存在则返回 None
|
|
81
|
+
"""
|
|
82
|
+
if not self.manifest.data_sources:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
ds = self.manifest.data_sources[0]
|
|
86
|
+
|
|
87
|
+
if ds.mode == "absolute":
|
|
88
|
+
# 绝对路径模式:直接使用配置的路径
|
|
89
|
+
if os.path.isdir(ds.path):
|
|
90
|
+
return os.path.normpath(ds.path)
|
|
91
|
+
else:
|
|
92
|
+
logger.warning(f"[DataSourceResolver] 数据源目录不存在(绝对路径): {ds.path}")
|
|
93
|
+
return None
|
|
94
|
+
else:
|
|
95
|
+
# 相对路径模式:基于项目根目录拼接
|
|
96
|
+
relative_path = os.path.join(self.project_root, ds.path)
|
|
97
|
+
if os.path.isdir(relative_path):
|
|
98
|
+
return os.path.normpath(relative_path)
|
|
99
|
+
else:
|
|
100
|
+
logger.warning(f"[DataSourceResolver] 数据源目录不存在(相对路径): {relative_path}")
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def resolve_source_path(self, data_directory: str, schema_file: TableSchemaFile) -> tuple[str | None, str | None]:
|
|
104
|
+
"""
|
|
105
|
+
@methoddesc 根据 Schema 定义解析数据源文件路径
|
|
106
|
+
|
|
107
|
+
按照以下优先级解析文件路径:
|
|
108
|
+
1. Schema 中显式配置的 source 路径(绝对文件或相对文件)
|
|
109
|
+
2. 自动发现:在数据目录中按表名搜索匹配的数据文件
|
|
110
|
+
|
|
111
|
+
参数:
|
|
112
|
+
data_directory: 数据文件搜索目录
|
|
113
|
+
schema_file: 表 Schema 定义文件
|
|
114
|
+
|
|
115
|
+
返回:
|
|
116
|
+
(文件路径, Sheet 名称) 元组,未找到则返回 (None, None)
|
|
117
|
+
"""
|
|
118
|
+
# 提取 Sheet 名称
|
|
119
|
+
sheet_name = schema_file.sheet
|
|
120
|
+
if not sheet_name and schema_file.source:
|
|
121
|
+
sheet_name = schema_file.source.sheet
|
|
122
|
+
|
|
123
|
+
# 优先级 1:Schema 显式配置了 source 路径
|
|
124
|
+
if schema_file.source:
|
|
125
|
+
src = schema_file.source
|
|
126
|
+
if src.mode == "absolute_file":
|
|
127
|
+
return src.path, sheet_name
|
|
128
|
+
|
|
129
|
+
src_path = src.path
|
|
130
|
+
src_path_abs = os.path.normpath(os.path.join(self.project_root, src_path))
|
|
131
|
+
|
|
132
|
+
if os.path.isfile(src_path_abs):
|
|
133
|
+
return src_path_abs, sheet_name
|
|
134
|
+
|
|
135
|
+
# 处理路径中多余的项目目录前缀(如 data/users.xlsx 中的 data 部分)
|
|
136
|
+
data_dir_basename = os.path.basename(self.project_root)
|
|
137
|
+
src_path_parts = normalize_to_posix(src_path).split("/")
|
|
138
|
+
if len(src_path_parts) > 1 and src_path_parts[0] == data_dir_basename:
|
|
139
|
+
src_path_adjusted = "/".join(src_path_parts[1:])
|
|
140
|
+
adjusted_path = os.path.normpath(os.path.join(self.project_root, src_path_adjusted))
|
|
141
|
+
if os.path.isfile(adjusted_path):
|
|
142
|
+
return adjusted_path, sheet_name
|
|
143
|
+
|
|
144
|
+
final_path = os.path.normpath(os.path.join(self.project_root, src_path))
|
|
145
|
+
return final_path, sheet_name
|
|
146
|
+
|
|
147
|
+
# 优先级 2:自动发现数据文件
|
|
148
|
+
table_name = schema_file.name or schema_file.id
|
|
149
|
+
|
|
150
|
+
# 如果指定了 Sheet 名称,说明是 Excel 文件,只搜索 Excel 扩展名
|
|
151
|
+
if sheet_name:
|
|
152
|
+
extensions = [".xlsx", ".xls"]
|
|
153
|
+
else:
|
|
154
|
+
extensions = [".xlsx", ".xls", ".csv", ".json", ".jsonl"]
|
|
155
|
+
|
|
156
|
+
# 确定搜索目录,优先使用 manifest 中配置的数据源目录
|
|
157
|
+
search_directory = data_directory
|
|
158
|
+
first_data_source = self.resolve_first_data_source()
|
|
159
|
+
if first_data_source:
|
|
160
|
+
search_directory = first_data_source
|
|
161
|
+
|
|
162
|
+
# 先在搜索目录根目录中查找
|
|
163
|
+
for ext in extensions:
|
|
164
|
+
file_path = os.path.join(search_directory, f"{table_name}{ext}")
|
|
165
|
+
if os.path.isfile(file_path):
|
|
166
|
+
return file_path, sheet_name
|
|
167
|
+
|
|
168
|
+
# 递归搜索子目录,跳过常见非数据目录
|
|
169
|
+
for root, dirs, files in os.walk(search_directory):
|
|
170
|
+
dirs[:] = [
|
|
171
|
+
d for d in dirs if not d.startswith(".") and d not in ["node_modules", "__pycache__", ".git", ".venv"]
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
for ext in extensions:
|
|
175
|
+
file_name = f"{table_name}{ext}"
|
|
176
|
+
if file_name in files:
|
|
177
|
+
file_path = os.path.join(root, file_name)
|
|
178
|
+
return file_path, sheet_name
|
|
179
|
+
|
|
180
|
+
return None, None
|