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,220 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""@fileoverview 校验结果多渠道报告服务主入口
|
|
17
|
+
|
|
18
|
+
功能概述:
|
|
19
|
+
- ReportService 统一管理和调度所有激活的报告者策略
|
|
20
|
+
- 支持本地文件、邮件、企业微信、飞书、钉钉等多种报告渠道
|
|
21
|
+
- 从 YAML 配置文件读取报告器配置,动态加载和初始化
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 注册表模式: _reporter_registry 将配置名称映射到具体报告器类
|
|
25
|
+
- 配置驱动: 通过 reporting_config.yaml 控制启用哪些报告器
|
|
26
|
+
- 全局配置注入: 自动将全局配置(如 log_dir)合并到各报告器的配置中
|
|
27
|
+
- 故障隔离: 单个报告器配置失败不影响其他报告器的正常工作
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
YAML 配置文件 (reporting_config.yaml):
|
|
31
|
+
reporters:
|
|
32
|
+
local_file:
|
|
33
|
+
enabled: true
|
|
34
|
+
email:
|
|
35
|
+
enabled: true
|
|
36
|
+
smtp_server: "smtp.example.com"
|
|
37
|
+
smtp_port: 587
|
|
38
|
+
sender_email: "precis@example.com"
|
|
39
|
+
sender_password: "secret"
|
|
40
|
+
receiver_email: "admin@example.com"
|
|
41
|
+
|
|
42
|
+
调用代码:
|
|
43
|
+
report_service = ReportService(base_dir="/path/to/project")
|
|
44
|
+
report_service.report(errors)
|
|
45
|
+
|
|
46
|
+
输出示例:
|
|
47
|
+
控制台输出:
|
|
48
|
+
报告服务已初始化。
|
|
49
|
+
正在配置报告者: 'local_file'...
|
|
50
|
+
[LocalFileReporter] 日志目录已创建: /path/to/project/logs
|
|
51
|
+
正在配置报告者: 'email'...
|
|
52
|
+
[EmailReporter] ✓ 邮件服务配置验证成功,发件人: precis@example.com
|
|
53
|
+
报告服务配置完成,共激活 2 个报告者。
|
|
54
|
+
--- 开始报告 5 个错误 ---
|
|
55
|
+
[LocalFileReporter] ✓ 成功将错误报告写入本地日志: ...
|
|
56
|
+
[EmailReporter] ✓ 成功发送错误报告邮件至 admin@example.com。
|
|
57
|
+
--- 报告流程结束 ---
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
import json
|
|
61
|
+
import logging
|
|
62
|
+
import os
|
|
63
|
+
from collections.abc import Callable
|
|
64
|
+
|
|
65
|
+
import yaml
|
|
66
|
+
|
|
67
|
+
from .reporters import (
|
|
68
|
+
DingTalkAppReporter,
|
|
69
|
+
EmailReporter,
|
|
70
|
+
FeishuReporter,
|
|
71
|
+
LocalFileReporter,
|
|
72
|
+
Reporter,
|
|
73
|
+
WeComAppReporter,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
logger = logging.getLogger(__name__)
|
|
77
|
+
|
|
78
|
+
# ==============================================================================
|
|
79
|
+
# 报告服务中心
|
|
80
|
+
# ==============================================================================
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ReportService:
|
|
84
|
+
"""
|
|
85
|
+
@classdesc 报告服务中心类。
|
|
86
|
+
|
|
87
|
+
负责管理和调度所有激活的报告者策略。
|
|
88
|
+
在初始化时从配置文件加载需要启用的报告者,
|
|
89
|
+
在报告时将错误信息发送给所有激活的报告者。
|
|
90
|
+
|
|
91
|
+
【属性说明】
|
|
92
|
+
- base_dir: 项目根目录路径
|
|
93
|
+
- config_path: 报告配置文件完整路径
|
|
94
|
+
- _reporter_registry: 报告者注册表(名称到类的映射)
|
|
95
|
+
- _active_reporters: 已激活的报告者实例列表
|
|
96
|
+
|
|
97
|
+
【使用示例】
|
|
98
|
+
>>> report_service = ReportService(base_dir='/path/to/project')
|
|
99
|
+
>>> errors = [{'error_type': 'ValidationError', 'message': '数据校验失败'}]
|
|
100
|
+
>>> report_service.report(errors)
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, base_dir: str, config_filename: str = "reporting_config.yaml"):
|
|
104
|
+
"""
|
|
105
|
+
初始化报告服务中心。
|
|
106
|
+
|
|
107
|
+
【初始化流程】
|
|
108
|
+
1. 设置项目根目录和配置文件路径
|
|
109
|
+
2. 初始化报告者注册表
|
|
110
|
+
3. 调用 _load_and_configure_reporters 加载配置
|
|
111
|
+
|
|
112
|
+
:param base_dir: 项目根目录路径
|
|
113
|
+
:param config_filename: 配置文件名,默认 'reporting_config.yaml'
|
|
114
|
+
"""
|
|
115
|
+
self.base_dir = base_dir
|
|
116
|
+
self.config_path = os.path.join(base_dir, config_filename)
|
|
117
|
+
|
|
118
|
+
# 报告者注册表:将配置中的名称映射到对应的报告者类
|
|
119
|
+
self._reporter_registry: dict[str, Callable[[], Reporter]] = {
|
|
120
|
+
"local_file": LocalFileReporter,
|
|
121
|
+
"email": EmailReporter,
|
|
122
|
+
"wecom": WeComAppReporter,
|
|
123
|
+
"feishu": FeishuReporter,
|
|
124
|
+
"dingtalk": DingTalkAppReporter,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# 激活的报告者实例列表
|
|
128
|
+
self._active_reporters: list[Reporter] = []
|
|
129
|
+
logger.info("报告服务已初始化。")
|
|
130
|
+
self._load_and_configure_reporters()
|
|
131
|
+
|
|
132
|
+
def _load_and_configure_reporters(self) -> None:
|
|
133
|
+
"""
|
|
134
|
+
@methoddesc 从配置文件加载并配置所有需要启用的报告者。
|
|
135
|
+
|
|
136
|
+
【处理流程】
|
|
137
|
+
1. 检查配置文件是否存在
|
|
138
|
+
2. 读取并解析 YAML 配置文件
|
|
139
|
+
3. 遍历配置中的 reporters 节点
|
|
140
|
+
4. 对每个启用的报告者进行实例化和配置
|
|
141
|
+
5. 将配置成功的报告者添加到激活列表
|
|
142
|
+
|
|
143
|
+
【配置合并规则】
|
|
144
|
+
- 全局配置(如 log_dir)会被自动注入
|
|
145
|
+
- 报告者特定配置会覆盖全局配置
|
|
146
|
+
"""
|
|
147
|
+
# 检查配置文件是否存在
|
|
148
|
+
if not os.path.exists(self.config_path):
|
|
149
|
+
logger.warning("报告配置文件 '%s' 未找到, 将不启用任何报告服务。", self.config_path)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
# 读取并解析 YAML 配置文件
|
|
154
|
+
with open(self.config_path, encoding="utf-8") as f:
|
|
155
|
+
config = yaml.safe_load(f) or {}
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error("读取报告配置文件失败: %s", e)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# 全局配置,可被所有报告者共享
|
|
161
|
+
global_config = {"log_dir": os.path.join(self.base_dir, "logs")}
|
|
162
|
+
|
|
163
|
+
# 遍历配置文件中定义的 reporters(§1.29: reporters 键为 null 时 `or {}` 兜底,
|
|
164
|
+
# 单条目为 null 时跳过并警告——报告是"坏了就跳过"的可选件,不拖崩校验流程)
|
|
165
|
+
reporters = config.get("reporters") or {}
|
|
166
|
+
for reporter_name, reporter_config in reporters.items():
|
|
167
|
+
if reporter_config is None:
|
|
168
|
+
logger.warning("报告者 '%s' 配置为空,跳过。", reporter_name)
|
|
169
|
+
continue
|
|
170
|
+
# 检查报告者是否启用
|
|
171
|
+
if not reporter_config.get("enabled", False):
|
|
172
|
+
logger.info("报告者 '%s' 未启用,跳过。", reporter_name)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# 检查报告者是否在注册表中
|
|
176
|
+
if reporter_name in self._reporter_registry:
|
|
177
|
+
reporter_class = self._reporter_registry[reporter_name]
|
|
178
|
+
reporter_instance = reporter_class()
|
|
179
|
+
|
|
180
|
+
# 合并全局配置和特定配置
|
|
181
|
+
full_config = {**global_config, **reporter_config}
|
|
182
|
+
|
|
183
|
+
logger.info("正在配置报告者: '%s'...", reporter_name)
|
|
184
|
+
if reporter_instance.configure(**full_config):
|
|
185
|
+
self._active_reporters.append(reporter_instance)
|
|
186
|
+
else:
|
|
187
|
+
logger.warning("报告者 '%s' 配置失败,将不被激活。", reporter_name)
|
|
188
|
+
else:
|
|
189
|
+
logger.warning("在报告者注册表中未找到名为 '%s' 的报告者。", reporter_name)
|
|
190
|
+
|
|
191
|
+
logger.info("报告服务配置完成,共激活 %d 个报告者。", len(self._active_reporters))
|
|
192
|
+
|
|
193
|
+
def report(self, errors: list[dict]) -> None:
|
|
194
|
+
"""
|
|
195
|
+
@methoddesc 执行总报告流程。
|
|
196
|
+
遍历所有激活的报告者并调用它们的 report 方法。
|
|
197
|
+
|
|
198
|
+
【处理逻辑】
|
|
199
|
+
1. 如果错误列表为空,直接返回(无需报告)
|
|
200
|
+
2. 如果没有激活的报告者,将错误打印到控制台
|
|
201
|
+
3. 否则,遍历所有激活的报告者发送报告
|
|
202
|
+
|
|
203
|
+
:param errors: 错误信息字典列表
|
|
204
|
+
"""
|
|
205
|
+
# 无错误时直接返回
|
|
206
|
+
if not errors:
|
|
207
|
+
logger.info("未发现错误,无需报告。")
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
# 无激活报告者时,打印到控制台作为后备(保留 print 确保错误在低日志级别下仍可见)
|
|
211
|
+
if not self._active_reporters:
|
|
212
|
+
logger.warning("无激活的报告者,错误将不会被报告。仅在控制台显示:")
|
|
213
|
+
print(json.dumps(errors, indent=2, ensure_ascii=False))
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
# 遍历所有激活的报告者发送报告
|
|
217
|
+
logger.info("--- 开始报告 %d 个错误 ---", len(errors))
|
|
218
|
+
for reporter in self._active_reporters:
|
|
219
|
+
reporter.report(errors)
|
|
220
|
+
logger.info("--- 报告流程结束 ---")
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
- 提供 Reporter 抽象基类统一入口
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 注册表模式: 各报告器实现继承自 Reporter 基类
|
|
25
|
+
- 统一导出: 通过 __all__ 控制对外可见的报告器列表
|
|
26
|
+
- 便于扩展: 新增报告器时只需在此添加导入和 __all__ 列表
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
从外部导入所需的报告器:
|
|
30
|
+
from backend.app.shared.core.reporter.reporters import (
|
|
31
|
+
Reporter, # 抽象基类,用于自定义报告器
|
|
32
|
+
LocalFileReporter, # 本地文件报告器
|
|
33
|
+
EmailReporter, # 邮件报告器
|
|
34
|
+
WeComAppReporter, # 企业微信报告器
|
|
35
|
+
FeishuReporter, # 飞书报告器
|
|
36
|
+
DingTalkAppReporter # 钉钉报告器
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
输出示例:
|
|
40
|
+
导出的报告器列表:
|
|
41
|
+
__all__ = [
|
|
42
|
+
"Reporter",
|
|
43
|
+
"LocalFileReporter",
|
|
44
|
+
"EmailReporter",
|
|
45
|
+
"WeComAppReporter",
|
|
46
|
+
"FeishuReporter",
|
|
47
|
+
"DingTalkAppReporter",
|
|
48
|
+
]
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from .base import Reporter
|
|
52
|
+
from .dingtalk_app_reporter import DingTalkAppReporter
|
|
53
|
+
from .email_reporter import EmailReporter
|
|
54
|
+
from .feishu_app_reporter import FeishuReporter
|
|
55
|
+
from .local_file_reporter import LocalFileReporter
|
|
56
|
+
from .wecom_app_reporter import WeComAppReporter
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"Reporter",
|
|
60
|
+
"LocalFileReporter",
|
|
61
|
+
"EmailReporter",
|
|
62
|
+
"WeComAppReporter",
|
|
63
|
+
"FeishuReporter",
|
|
64
|
+
"DingTalkAppReporter",
|
|
65
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""@fileoverview 报告器抽象基类
|
|
17
|
+
|
|
18
|
+
功能概述:
|
|
19
|
+
- 定义所有报告器的统一接口(Strategy 模式)
|
|
20
|
+
- 提供 configure() 和 report() 抽象方法
|
|
21
|
+
- 强制子类实现配置和报告的标准化流程
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 策略模式 (Strategy Pattern): 将不同的报告渠道封装为可互换的算法
|
|
25
|
+
- 模板方法模式: 定义 report() 和 configure() 的标准接口,子类负责具体实现
|
|
26
|
+
- 依赖倒置: 高层模块(ReportService)依赖抽象(Reporter),而非具体实现
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
子类实现示例:
|
|
30
|
+
class MyReporter(Reporter):
|
|
31
|
+
def configure(self, **config) -> bool:
|
|
32
|
+
# 验证并保存配置
|
|
33
|
+
self.webhook_url = config.get("webhook_url")
|
|
34
|
+
return bool(self.webhook_url)
|
|
35
|
+
|
|
36
|
+
def report(self, errors: list[dict]):
|
|
37
|
+
# 发送错误报告到自定义渠道
|
|
38
|
+
requests.post(self.webhook_url, json=errors)
|
|
39
|
+
|
|
40
|
+
输出示例:
|
|
41
|
+
客户端代码无需关心具体实现:
|
|
42
|
+
reporter: Reporter = EmailReporter() # 或任何其他报告器
|
|
43
|
+
if reporter.configure(**config):
|
|
44
|
+
reporter.report(errors)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from abc import ABC, abstractmethod
|
|
48
|
+
from typing import Any
|
|
49
|
+
from urllib.parse import urlparse
|
|
50
|
+
|
|
51
|
+
# 截断后追加的省略标记(各报告器共用,保证提示语义一致)
|
|
52
|
+
TRUNCATION_SUFFIX = "\n... (内容过长,已截断)"
|
|
53
|
+
|
|
54
|
+
# 各报告器的消息模板在 error_details 之外还含标题/时间戳/代码块围栏等固定文本,
|
|
55
|
+
# 截断上限需为其预留字节余量,保证整条消息编码后不超过平台限额
|
|
56
|
+
TEMPLATE_HEADROOM_BYTES = 256
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def sanitize_url_for_log(url: str) -> str:
|
|
60
|
+
"""脱敏 URL 用于日志输出:只保留 scheme://host/path,丢弃 query/fragment。
|
|
61
|
+
|
|
62
|
+
Webhook/Token 类 URL 的凭证(token、corpsecret 等)几乎都在 query 中,
|
|
63
|
+
异常日志打印完整 URL 会把密钥写进日志文件。此函数只保留主机与路径。
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
url: 原始 URL(可能含敏感 query)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
形如 "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" 的脱敏 URL;
|
|
70
|
+
解析失败时返回 "<invalid-url>"(不抛异常,日志路径不允许二次失败)。
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
parsed = urlparse(url)
|
|
74
|
+
if parsed.scheme and parsed.netloc:
|
|
75
|
+
return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
|
|
76
|
+
return "<invalid-url>"
|
|
77
|
+
except Exception: # noqa: BLE001 日志脱敏兜底,任何解析异常都降级为占位符
|
|
78
|
+
return "<invalid-url>"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def truncate_to_byte_limit(text: str, max_bytes: int, suffix: str = TRUNCATION_SUFFIX) -> str:
|
|
82
|
+
"""按 UTF-8 字节数上限截断文本,超限时追加省略标记。
|
|
83
|
+
|
|
84
|
+
平台(飞书/钉钉/企业微信)的消息限额按字节数计算。过去"按字节检查、按字符截断"
|
|
85
|
+
的做法对中文内容无效:每个汉字 UTF-8 占 3 字节,按字符截掉一半后实际字节数
|
|
86
|
+
仍可能超限,消息会被平台拒收。
|
|
87
|
+
|
|
88
|
+
规则:
|
|
89
|
+
- 原文未超限则原样返回;
|
|
90
|
+
- 超限时按编码后字节累积截断,并回退到 UTF-8 字符边界(不截在多字节字符中间),
|
|
91
|
+
追加省略标记后总长(含标记)仍不超过 max_bytes。
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
text: 原始文本
|
|
95
|
+
max_bytes: 允许的最大 UTF-8 字节数
|
|
96
|
+
suffix: 截断时追加的省略标记
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
截断后的字符串(UTF-8 编码后不超过 max_bytes 字节)
|
|
100
|
+
"""
|
|
101
|
+
encoded = text.encode("utf-8")
|
|
102
|
+
if len(encoded) <= max_bytes:
|
|
103
|
+
return text
|
|
104
|
+
|
|
105
|
+
suffix_bytes = suffix.encode("utf-8")
|
|
106
|
+
limit = max_bytes - len(suffix_bytes)
|
|
107
|
+
if limit <= 0:
|
|
108
|
+
# 防御:上限比省略标记还小,直接返回空串,保证总长不超限
|
|
109
|
+
return ""
|
|
110
|
+
|
|
111
|
+
# 从末尾回退,跳过 UTF-8 多字节序列的续字节(0b10xxxxxx),避免截在字符中间
|
|
112
|
+
while limit > 0 and (encoded[limit] & 0xC0) == 0x80:
|
|
113
|
+
limit -= 1
|
|
114
|
+
|
|
115
|
+
return encoded[:limit].decode("utf-8", errors="ignore") + suffix
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Reporter(ABC):
|
|
119
|
+
"""
|
|
120
|
+
@classdesc 报告器抽象基类
|
|
121
|
+
|
|
122
|
+
该类定义了所有报告器必须实现的接口规范。
|
|
123
|
+
采用策略模式,任何需要报告数据验证错误的模块都可以通过统一的接口调用不同的报告策略。
|
|
124
|
+
|
|
125
|
+
属性说明:
|
|
126
|
+
name: 报告器实例的名称,用于日志和调试标识
|
|
127
|
+
|
|
128
|
+
使用示例:
|
|
129
|
+
# 客户端代码不关心具体使用哪种报告器
|
|
130
|
+
reporter: Reporter = EmailReporter()
|
|
131
|
+
reporter.configure(**email_config)
|
|
132
|
+
reporter.report(errors)
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self, name: str):
|
|
136
|
+
"""
|
|
137
|
+
初始化报告器实例
|
|
138
|
+
|
|
139
|
+
:param name: 报告器的名称,用于日志输出和调试追踪
|
|
140
|
+
"""
|
|
141
|
+
self.name = name
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
def configure(self, **config: Any) -> bool:
|
|
145
|
+
"""
|
|
146
|
+
@methoddesc 配置当前报告器
|
|
147
|
+
|
|
148
|
+
这是一个抽象方法,每个子类必须实现自己的配置逻辑。
|
|
149
|
+
配置通常包括连接信息、认证凭证、目标路径等。
|
|
150
|
+
|
|
151
|
+
:param config: 关键字参数,包含特定报告器的配置选项
|
|
152
|
+
不同的报告器子类有不同的配置要求
|
|
153
|
+
:return: 配置是否成功。返回 True 表示配置生效,可以调用 report();
|
|
154
|
+
返回 False 表示配置失败,需要检查配置参数或日志
|
|
155
|
+
|
|
156
|
+
子类实现注意事项:
|
|
157
|
+
- 应该验证所有必需的配置参数
|
|
158
|
+
- 应该在失败时打印详细的错误信息
|
|
159
|
+
- 可以选择在配置阶段建立连接(如邮件SMTP连接)进行验证
|
|
160
|
+
"""
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
@abstractmethod
|
|
164
|
+
def report(self, errors: list[dict]) -> None:
|
|
165
|
+
"""
|
|
166
|
+
@methoddesc 执行报告操作,将错误信息发送或写入指定目标
|
|
167
|
+
|
|
168
|
+
这是一个抽象方法,每个子类必须实现自己的报告逻辑。
|
|
169
|
+
在调用此方法之前,必须确保 configure() 已经成功调用。
|
|
170
|
+
|
|
171
|
+
:param errors: 错误信息的字典列表
|
|
172
|
+
每个字典代表一个独立的验证错误
|
|
173
|
+
标准格式包含:row_index, column, value, error_type, error_message
|
|
174
|
+
不同报告器可能对格式有特定要求
|
|
175
|
+
|
|
176
|
+
错误字典的推荐字段:
|
|
177
|
+
- row_index: 错误发生的行号(可选,列级别错误可能为 None)
|
|
178
|
+
- column: 涉及的数据列名
|
|
179
|
+
- value: 导致错误的原始值
|
|
180
|
+
- error_type: 错误类型标识(如 'TypeValidationError')
|
|
181
|
+
- error_message: 人类可读的错误描述
|
|
182
|
+
|
|
183
|
+
子类实现注意事项:
|
|
184
|
+
- 应该检查是否已正确配置(如检查 is_configured 标志)
|
|
185
|
+
- 应该处理空列表的情况(空列表通常不需要报告)
|
|
186
|
+
- 应该在失败时打印错误日志,但不应抛出异常
|
|
187
|
+
"""
|
|
188
|
+
pass
|