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,268 @@
|
|
|
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
|
+
- 通过企业微信自建应用发送数据验证错误报告
|
|
20
|
+
- 支持 Markdown 格式消息和自动 access_token 刷新
|
|
21
|
+
- 实现 access_token 的缓存和自动刷新机制(提前 5 分钟刷新)
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 继承 Reporter 抽象基类,实现 configure() 和 report() 方法
|
|
25
|
+
- Token 管理: _get_access_token() 实现缓存 + 自动刷新策略
|
|
26
|
+
- 消息截断: 企业微信消息限制约 2KB,超长内容自动截断
|
|
27
|
+
- 统一请求: _send_request() 封装 HTTP POST 请求和错误处理
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
配置参数:
|
|
31
|
+
{
|
|
32
|
+
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
|
33
|
+
"corp_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
34
|
+
"agent_id": 1000002,
|
|
35
|
+
"touser": "@all"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
错误数据:
|
|
39
|
+
[{"error_type": "ValidationError", "row_index": 2, "column": "age", "value": "abc"}]
|
|
40
|
+
|
|
41
|
+
输出示例:
|
|
42
|
+
企业微信消息 (Markdown 格式):
|
|
43
|
+
## <font color="warning">【数据验证警告】发现 1 个错误!</font>
|
|
44
|
+
**请相关人员尽快处理。**
|
|
45
|
+
**错误详情:**
|
|
46
|
+
```json
|
|
47
|
+
[{"error_type": "ValidationError", ...}]
|
|
48
|
+
```
|
|
49
|
+
> 报告时间: 2024-01-15 14:30:52
|
|
50
|
+
|
|
51
|
+
控制台输出:
|
|
52
|
+
[WeComAppReporter] ✓ 配置成功。
|
|
53
|
+
[WeComAppReporter] ✓ 成功获取 Access Token。
|
|
54
|
+
[WeComAppReporter] ✓ 成功发送消息。
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
import json
|
|
58
|
+
import logging
|
|
59
|
+
import time
|
|
60
|
+
import urllib.request
|
|
61
|
+
from datetime import datetime
|
|
62
|
+
from typing import Any
|
|
63
|
+
|
|
64
|
+
from .base import TEMPLATE_HEADROOM_BYTES, Reporter, sanitize_url_for_log, truncate_to_byte_limit
|
|
65
|
+
|
|
66
|
+
logger = logging.getLogger(__name__)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class WeComAppReporter(Reporter):
|
|
70
|
+
"""
|
|
71
|
+
@classdesc 企业微信自建应用消息推送报告器
|
|
72
|
+
|
|
73
|
+
通过企业微信自建应用发送消息,将数据验证错误报告推送给指定用户。
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
config: 存储配置参数的字典,包含 corp_id, corp_secret, agent_id, touser
|
|
77
|
+
is_configured: 配置是否有效的标志
|
|
78
|
+
access_token: 企业微信 API 访问令牌
|
|
79
|
+
token_expires_at: 令牌过期时间戳
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
@methoddesc 初始化企业微信报告器
|
|
85
|
+
|
|
86
|
+
设置默认属性,包括配置字典、配置状态标志、
|
|
87
|
+
访问令牌及其过期时间戳。
|
|
88
|
+
"""
|
|
89
|
+
super().__init__("WeComAppReporter")
|
|
90
|
+
self.config: dict[str, Any] = {} # 存储配置参数
|
|
91
|
+
self.is_configured = False # 配置是否有效
|
|
92
|
+
self.access_token = None # 企业微信 API 访问令牌
|
|
93
|
+
self.token_expires_at = 0 # 令牌过期时间戳
|
|
94
|
+
|
|
95
|
+
def configure(self, **kwargs: Any) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
@methoddesc 配置企业微信报告器参数
|
|
98
|
+
|
|
99
|
+
验证所有必需参数是否提供,并保存配置信息。
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
corp_id: 企业 ID(企业在企业微信中的唯一标识)
|
|
103
|
+
corp_secret: 应用 Secret(用于获取 access_token)
|
|
104
|
+
agent_id: 应用 AgentID(企业微信应用编号)
|
|
105
|
+
touser: 接收者用户账号(@all 表示发送给全部成员)
|
|
106
|
+
**kwargs: 额外参数(保留用于扩展)
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
bool: 配置是否成功
|
|
110
|
+
"""
|
|
111
|
+
# 参数验证:确保所有必需参数都提供
|
|
112
|
+
corp_id = kwargs.get("corp_id")
|
|
113
|
+
corp_secret = kwargs.get("corp_secret")
|
|
114
|
+
agent_id = kwargs.get("agent_id")
|
|
115
|
+
touser = kwargs.get("touser")
|
|
116
|
+
if not all([corp_id, corp_secret, agent_id, touser]):
|
|
117
|
+
self.is_configured = False
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# 显式处理 None,避免 mypy 将 Any | None 传给 int() 时报错
|
|
121
|
+
if agent_id is None:
|
|
122
|
+
self.is_configured = False
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
# 保存配置参数
|
|
126
|
+
self.config = {
|
|
127
|
+
"corp_id": str(corp_id),
|
|
128
|
+
"corp_secret": str(corp_secret),
|
|
129
|
+
"agent_id": int(agent_id),
|
|
130
|
+
"touser": str(touser),
|
|
131
|
+
}
|
|
132
|
+
self.is_configured = True
|
|
133
|
+
print(f"[{self.name}] ✓ 配置成功。")
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
def _get_access_token(self) -> str | None:
|
|
137
|
+
"""
|
|
138
|
+
@methoddesc 获取或刷新企业微信 API 访问令牌(Access Token)
|
|
139
|
+
|
|
140
|
+
访问令牌用于调用企业微信 API,有效期默认为 7200 秒(2 小时)。
|
|
141
|
+
实现自动刷新机制:在过期前 5 分钟自动刷新,避免令牌失效。
|
|
142
|
+
|
|
143
|
+
Token 刷新逻辑:
|
|
144
|
+
- 检查当前时间是否早于过期时间
|
|
145
|
+
- 如果是,直接返回缓存的令牌
|
|
146
|
+
- 否则调用企业微信 API 获取新令牌并更新缓存
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
str: 有效的访问令牌,如果获取失败返回 None
|
|
150
|
+
|
|
151
|
+
API 调用:
|
|
152
|
+
- URL: https://qyapi.weixin.qq.com/cgi-bin/gettoken
|
|
153
|
+
- 参数: corpid, corpsecret
|
|
154
|
+
- 响应: access_token, expires_in (有效期秒数)
|
|
155
|
+
"""
|
|
156
|
+
# 检查令牌是否仍然有效(提前 5 分钟刷新)
|
|
157
|
+
if time.time() < self.token_expires_at:
|
|
158
|
+
return self.access_token
|
|
159
|
+
|
|
160
|
+
# 调用企业微信 API 获取令牌
|
|
161
|
+
url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.config['corp_id']}&corpsecret={self.config['corp_secret']}"
|
|
162
|
+
try:
|
|
163
|
+
# 发送请求
|
|
164
|
+
with urllib.request.urlopen(url, timeout=10) as response:
|
|
165
|
+
data = json.loads(response.read().decode())
|
|
166
|
+
if data.get("errcode") == 0:
|
|
167
|
+
# 成功获取令牌
|
|
168
|
+
self.access_token = data["access_token"]
|
|
169
|
+
# 设置过期时间:当前时间 + 有效期 - 缓冲时间(5分钟)
|
|
170
|
+
self.token_expires_at = time.time() + data["expires_in"] - 300
|
|
171
|
+
print(f"[{self.name}] ✓ 成功获取 Access Token。")
|
|
172
|
+
return self.access_token
|
|
173
|
+
else:
|
|
174
|
+
print(f"[{self.name}] !! 错误: 获取 Access Token 失败: {data.get('errmsg')}")
|
|
175
|
+
return None
|
|
176
|
+
except Exception as e:
|
|
177
|
+
# gettoken URL 的 query 含 corpsecret,日志只打脱敏后的 host+path
|
|
178
|
+
logger.warning(
|
|
179
|
+
"[%s] !! 错误: 请求 Access Token 失败 (%s): %s",
|
|
180
|
+
self.name,
|
|
181
|
+
sanitize_url_for_log(url),
|
|
182
|
+
e,
|
|
183
|
+
)
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
def report(self, errors: list[dict]) -> None:
|
|
187
|
+
"""
|
|
188
|
+
@methoddesc 发送错误报告的主入口方法
|
|
189
|
+
|
|
190
|
+
流程:
|
|
191
|
+
1. 检查配置是否有效
|
|
192
|
+
2. 获取有效的访问令牌
|
|
193
|
+
3. 格式化错误信息为 Markdown
|
|
194
|
+
4. 调用企业微信发送应用消息 API
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
errors: 错误信息列表,每个元素是一个包含错误详情的字典
|
|
198
|
+
"""
|
|
199
|
+
# 前置检查:确保已正确配置
|
|
200
|
+
if not self.is_configured:
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# 步骤1: 获取有效的访问令牌
|
|
204
|
+
token = self._get_access_token()
|
|
205
|
+
if not token:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# 步骤2: 统计错误数量并格式化详情
|
|
209
|
+
error_count = len(errors)
|
|
210
|
+
error_details = json.dumps(errors, indent=2, ensure_ascii=False)
|
|
211
|
+
|
|
212
|
+
# 安全截断:企业微信消息内容限制约 2KB
|
|
213
|
+
# 必须按 UTF-8 字节截断:平台限额按字节计,中文按字符截断后实际字节数
|
|
214
|
+
# 仍可能超限被拒收(见 truncate_to_byte_limit)
|
|
215
|
+
error_details = truncate_to_byte_limit(error_details, 2048 - TEMPLATE_HEADROOM_BYTES)
|
|
216
|
+
|
|
217
|
+
# 步骤3: 构建 Markdown 格式消息内容
|
|
218
|
+
# 企业微信支持部分 HTML 标签和字体颜色
|
|
219
|
+
content = f"""## <font color=\"warning\">【数据验证警告】发现 {error_count} 个错误!</font>
|
|
220
|
+
**请相关人员尽快处理。**
|
|
221
|
+
**错误详情:**
|
|
222
|
+
```json
|
|
223
|
+
{error_details}
|
|
224
|
+
```
|
|
225
|
+
> 报告时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
# 步骤4: 构建发送应用消息的请求负载
|
|
229
|
+
payload = {
|
|
230
|
+
"touser": self.config["touser"], # 接收者:用户账号
|
|
231
|
+
"msgtype": "markdown", # 消息类型:Markdown
|
|
232
|
+
"agentid": self.config["agent_id"], # 应用 AgentID
|
|
233
|
+
"markdown": {
|
|
234
|
+
"content": content # 消息正文内容
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# 步骤5: 发送消息
|
|
239
|
+
url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}"
|
|
240
|
+
self._send_request(url, payload)
|
|
241
|
+
|
|
242
|
+
def _send_request(self, url: str, payload: dict[str, Any]) -> None:
|
|
243
|
+
"""
|
|
244
|
+
@methoddesc 发送 HTTP POST 请求到企业微信 API
|
|
245
|
+
|
|
246
|
+
处理所有企业微信 API 的 HTTP 通信,包括错误处理和日志记录。
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
url: 请求目标 URL
|
|
250
|
+
payload: 请求体数据字典
|
|
251
|
+
"""
|
|
252
|
+
try:
|
|
253
|
+
# 序列化请求负载为 JSON 字节
|
|
254
|
+
data = json.dumps(payload).encode("utf-8")
|
|
255
|
+
headers = {"Content-Type": "application/json"}
|
|
256
|
+
|
|
257
|
+
# 构建 HTTP 请求
|
|
258
|
+
req = urllib.request.Request(url, data=data, headers=headers)
|
|
259
|
+
|
|
260
|
+
# 发送请求并获取响应
|
|
261
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
262
|
+
result = json.loads(response.read().decode("utf-8"))
|
|
263
|
+
if result.get("errcode") == 0:
|
|
264
|
+
print(f"[{self.name}] ✓ 成功发送消息。")
|
|
265
|
+
else:
|
|
266
|
+
print(f"[{self.name}] !! 错误: 发送消息失败: {result.get('errmsg')}")
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.warning("[%s] !! 错误: 发送请求失败: %s", self.name, e)
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
"""
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
"""路径工具函数:POSIX 风格规范化与跨平台路径等价比较。"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def normalize_to_posix(path: str) -> str:
|
|
25
|
+
"""将路径统一为 POSIX 格式(正斜杠、无连续斜杠、无末尾斜杠)。
|
|
26
|
+
|
|
27
|
+
不转换大小写(文件系统操作可能需要保留原始大小写)。
|
|
28
|
+
"""
|
|
29
|
+
if not path:
|
|
30
|
+
return ""
|
|
31
|
+
result = path.replace("\\", "/")
|
|
32
|
+
result = re.sub(r"/+", "/", result)
|
|
33
|
+
result = result.rstrip("/")
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def paths_equal(a: str, b: str, *, ignore_case: bool = True) -> bool:
|
|
38
|
+
"""标准化后比较两个路径是否等价。
|
|
39
|
+
|
|
40
|
+
默认忽略大小写(适用于 Windows 等不区分大小写的文件系统)。
|
|
41
|
+
"""
|
|
42
|
+
na = normalize_to_posix(a)
|
|
43
|
+
nb = normalize_to_posix(b)
|
|
44
|
+
if ignore_case:
|
|
45
|
+
return na.lower() == nb.lower()
|
|
46
|
+
return na == nb
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def make_relative(base: str, target: str) -> str:
|
|
50
|
+
"""计算 target 相对于 base 的相对路径,返回 POSIX 格式。
|
|
51
|
+
|
|
52
|
+
如果无法计算相对路径(例如 Windows 下跨驱动器),返回 target 的 POSIX 格式。
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
rel = os.path.relpath(target, base)
|
|
56
|
+
except ValueError:
|
|
57
|
+
# os.path.relpath 在 Windows 跨盘符(如 base=C:\,target=D:\data)时抛 ValueError,
|
|
58
|
+
# 按 docstring 承诺降级:返回 target 的 POSIX 格式而非向上抛异常
|
|
59
|
+
return normalize_to_posix(target)
|
|
60
|
+
return normalize_to_posix(rel)
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
- 从数据列值中按命名捕获组提取派生列数据
|
|
20
|
+
- 支持将提取结果应用到 pandas DataFrame
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
|
|
25
|
+
import pandas as pd
|
|
26
|
+
|
|
27
|
+
from app.shared.domain.regex_flags import parse_regex_flags
|
|
28
|
+
|
|
29
|
+
__all__ = ["extract_columns_from_values", "parse_regex_flags"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def extract_columns_from_values(
|
|
33
|
+
regex_pattern: str, regex_flags: str, case_sensitive: bool, values: list[str], match_mode: str = "extract"
|
|
34
|
+
) -> tuple[dict[str, list[str]], list[str], int, int]:
|
|
35
|
+
"""
|
|
36
|
+
@methoddesc 从值列表中提取命名捕获组数据。
|
|
37
|
+
|
|
38
|
+
【功能说明】
|
|
39
|
+
这是后端 /utils/regex/validate-extract 接口的核心逻辑封装,
|
|
40
|
+
可以在数据校验流程中直接调用,无需通过 HTTP 请求。
|
|
41
|
+
|
|
42
|
+
【参数说明】
|
|
43
|
+
:param regex_pattern: 正则表达式模式字符串
|
|
44
|
+
:param regex_flags: 正则标志字符串(如 "gi")
|
|
45
|
+
:param case_sensitive: 是否区分大小写
|
|
46
|
+
:param values: 目标列的值列表
|
|
47
|
+
:param match_mode: 匹配模式,"full" 或 "partial"(extract 本质也是 partial)
|
|
48
|
+
|
|
49
|
+
【返回值】
|
|
50
|
+
:return: (extracted_columns, group_names, match_count, error_count)
|
|
51
|
+
- extracted_columns: 提取的列数据字典
|
|
52
|
+
- group_names: 命名捕获组名称列表
|
|
53
|
+
- match_count: 匹配成功数量
|
|
54
|
+
- error_count: 匹配失败数量
|
|
55
|
+
"""
|
|
56
|
+
# flags 解析统一走 parse_regex_flags(整词匹配,防 "multiline" 误开 IGNORECASE)
|
|
57
|
+
flags = parse_regex_flags(regex_flags)
|
|
58
|
+
if not case_sensitive:
|
|
59
|
+
flags |= re.IGNORECASE
|
|
60
|
+
|
|
61
|
+
compiled = re.compile(regex_pattern, flags)
|
|
62
|
+
group_names = list(compiled.groupindex.keys())
|
|
63
|
+
extracted_columns: dict[str, list[str]] = {name: [] for name in group_names}
|
|
64
|
+
|
|
65
|
+
match_count = 0
|
|
66
|
+
error_count = 0
|
|
67
|
+
|
|
68
|
+
for value in values:
|
|
69
|
+
# §1.32: NaN 按空串处理(对齐 extractors.py 的 fillna("") 口径)——
|
|
70
|
+
# 原实现 str(nan)="nan" 参与 match/search,缺失行被计入匹配统计
|
|
71
|
+
value_str = "" if (value is None or pd.isna(value)) else str(value)
|
|
72
|
+
if match_mode == "full":
|
|
73
|
+
match = compiled.match(value_str)
|
|
74
|
+
else:
|
|
75
|
+
match = compiled.search(value_str)
|
|
76
|
+
|
|
77
|
+
if match:
|
|
78
|
+
match_count += 1
|
|
79
|
+
groups = match.groupdict()
|
|
80
|
+
for name in group_names:
|
|
81
|
+
extracted_columns[name].append("" if groups.get(name) is None else str(groups.get(name)))
|
|
82
|
+
else:
|
|
83
|
+
error_count += 1
|
|
84
|
+
for name in group_names:
|
|
85
|
+
extracted_columns[name].append("")
|
|
86
|
+
|
|
87
|
+
return extracted_columns, group_names, match_count, error_count
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def apply_extracted_columns_to_dataframe(
|
|
91
|
+
df: pd.DataFrame, extracted_columns: dict[str, list[str]], column_names: list[str]
|
|
92
|
+
) -> pd.DataFrame:
|
|
93
|
+
"""
|
|
94
|
+
@methoddesc 将提取的列数据应用到 DataFrame。
|
|
95
|
+
|
|
96
|
+
【功能说明】
|
|
97
|
+
将 extracted_columns 中的数据作为新列添加到 DataFrame。
|
|
98
|
+
|
|
99
|
+
【参数说明】
|
|
100
|
+
:param df: 原始 DataFrame
|
|
101
|
+
:param extracted_columns: 提取的列数据字典
|
|
102
|
+
:param column_names: 新列的名称列表(与 extracted_columns 的 key 对应)
|
|
103
|
+
|
|
104
|
+
【返回值】
|
|
105
|
+
:return: 添加了新列的 DataFrame
|
|
106
|
+
"""
|
|
107
|
+
result_df = df.copy()
|
|
108
|
+
|
|
109
|
+
for col_name in column_names:
|
|
110
|
+
if col_name in extracted_columns:
|
|
111
|
+
result_df[col_name] = extracted_columns[col_name]
|
|
112
|
+
|
|
113
|
+
return result_df
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview 领域模型包入口
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 统一导出数据校验相关的领域业务模型
|
|
21
|
+
- 提供数据集结构定义、数据类型系统、验证约束系统、表达式系统、数据处理引擎的集中入口
|
|
22
|
+
- 降低外部调用方的导入成本
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 聚合导出模式: 通过 __init__.py 将各子模块的核心符号集中暴露
|
|
26
|
+
- 模块间关系: 各子模块独立实现,通过本入口统一对外
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
from app.shared.domain import DataSetSchema, StringType, NotNullConstraint
|
|
30
|
+
|
|
31
|
+
输出示例:
|
|
32
|
+
# 可直接使用 ColumnSchema、process_dataframe、ExpressionRegistry 等核心类型和函数
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# 1. 项目内部导入
|
|
36
|
+
from app.shared.domain.data_engine import (
|
|
37
|
+
process_dataframe,
|
|
38
|
+
)
|
|
39
|
+
from app.shared.domain.data_types import (
|
|
40
|
+
CompositeConditionType,
|
|
41
|
+
DataType,
|
|
42
|
+
DecimalType,
|
|
43
|
+
ExpressionType,
|
|
44
|
+
FloatType,
|
|
45
|
+
IntegerType,
|
|
46
|
+
SequenceType,
|
|
47
|
+
SpecificCompositeConditionType,
|
|
48
|
+
SpecificExpressionType,
|
|
49
|
+
StringType,
|
|
50
|
+
)
|
|
51
|
+
from app.shared.domain.dataset_schema import (
|
|
52
|
+
TYPE_REGISTRY,
|
|
53
|
+
ColumnSchema,
|
|
54
|
+
DataSetSchema,
|
|
55
|
+
TableSchema,
|
|
56
|
+
build_type_from_config,
|
|
57
|
+
)
|
|
58
|
+
from app.shared.domain.expression_system import (
|
|
59
|
+
TYPE_CASTERS,
|
|
60
|
+
ExpressionPattern,
|
|
61
|
+
ExpressionRegistry,
|
|
62
|
+
create_templated_parser,
|
|
63
|
+
)
|
|
64
|
+
from app.shared.domain.validation_constraints import (
|
|
65
|
+
CONDITION_REGISTRY,
|
|
66
|
+
AllowedValuesConstraint,
|
|
67
|
+
CharsetConstraint,
|
|
68
|
+
CompositeConstraint,
|
|
69
|
+
ConditionalConstraint,
|
|
70
|
+
Constraint,
|
|
71
|
+
DateLogicConstraint,
|
|
72
|
+
ForeignKeyConstraints,
|
|
73
|
+
NotNullConstraint,
|
|
74
|
+
RangeConstraint,
|
|
75
|
+
ScriptedConstraint,
|
|
76
|
+
UniqueConstraint,
|
|
77
|
+
register_condition,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"DataSetSchema",
|
|
82
|
+
"TableSchema",
|
|
83
|
+
"ColumnSchema",
|
|
84
|
+
"build_type_from_config",
|
|
85
|
+
"TYPE_REGISTRY",
|
|
86
|
+
"DataType",
|
|
87
|
+
"IntegerType",
|
|
88
|
+
"StringType",
|
|
89
|
+
"FloatType",
|
|
90
|
+
"DecimalType",
|
|
91
|
+
"SequenceType",
|
|
92
|
+
"ExpressionType",
|
|
93
|
+
"SpecificExpressionType",
|
|
94
|
+
"CompositeConditionType",
|
|
95
|
+
"SpecificCompositeConditionType",
|
|
96
|
+
"CharsetConstraint",
|
|
97
|
+
"CompositeConstraint",
|
|
98
|
+
"Constraint",
|
|
99
|
+
"NotNullConstraint",
|
|
100
|
+
"UniqueConstraint",
|
|
101
|
+
"ForeignKeyConstraints",
|
|
102
|
+
"AllowedValuesConstraint",
|
|
103
|
+
"ConditionalConstraint",
|
|
104
|
+
"RangeConstraint",
|
|
105
|
+
"ScriptedConstraint",
|
|
106
|
+
"DateLogicConstraint",
|
|
107
|
+
"CONDITION_REGISTRY",
|
|
108
|
+
"register_condition",
|
|
109
|
+
"ExpressionRegistry",
|
|
110
|
+
"ExpressionPattern",
|
|
111
|
+
"create_templated_parser",
|
|
112
|
+
"TYPE_CASTERS",
|
|
113
|
+
"process_dataframe",
|
|
114
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
- 保持向后兼容,旧模块仍可通过 re-export 访问同名符号
|
|
22
|
+
- 集中管理 Unique、NotNull、Range、ForeignKey、Conditional 等约束实现
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 各约束子模块独立实现,继承自 Constraint 基类
|
|
26
|
+
- 通过 __init__.py 聚合导出,降低调用方导入成本
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
from app.shared.domain.constraints import NotNullConstraint, UniqueConstraint, CONDITION_REGISTRY
|
|
30
|
+
|
|
31
|
+
输出示例:
|
|
32
|
+
# 可直接使用所有约束类及 register_condition 注册装饰器
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# 1. 项目内部导入: 从各子模块导入具体的约束实现类
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# 每个约束类型独立一个模块,继承自 Constraint 基类
|
|
39
|
+
# 通过包入口统一聚合导出,降低调用方的导入成本
|
|
40
|
+
|
|
41
|
+
from app.shared.domain.constraints.allowed_values import AllowedValuesConstraint
|
|
42
|
+
from app.shared.domain.constraints.base import Constraint
|
|
43
|
+
from app.shared.domain.constraints.charset import CharsetConstraint
|
|
44
|
+
from app.shared.domain.constraints.composite import CompositeConstraint
|
|
45
|
+
from app.shared.domain.constraints.condition_registry import CONDITION_REGISTRY, register_condition
|
|
46
|
+
from app.shared.domain.constraints.conditional import ConditionalConstraint
|
|
47
|
+
from app.shared.domain.constraints.date_logic import DateLogicConstraint
|
|
48
|
+
from app.shared.domain.constraints.foreign_key import ForeignKeyConstraints
|
|
49
|
+
from app.shared.domain.constraints.not_null import NotNullConstraint
|
|
50
|
+
from app.shared.domain.constraints.range import RangeConstraint
|
|
51
|
+
from app.shared.domain.constraints.regex import RegexConstraint
|
|
52
|
+
from app.shared.domain.constraints.scripted import ScriptedConstraint
|
|
53
|
+
from app.shared.domain.constraints.unique import UniqueConstraint
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# __all__ 控制 from module import * 时导出的符号列表
|
|
57
|
+
# ============================================================================
|
|
58
|
+
# 显式声明对外暴露的公共 API,避免导入内部实现细节
|
|
59
|
+
# 新增约束类型时需同步更新此列表,否则无法通过通配符导入
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
"AllowedValuesConstraint",
|
|
63
|
+
"CharsetConstraint",
|
|
64
|
+
"CompositeConstraint",
|
|
65
|
+
"CONDITION_REGISTRY",
|
|
66
|
+
"ConditionalConstraint",
|
|
67
|
+
"Constraint",
|
|
68
|
+
"DateLogicConstraint",
|
|
69
|
+
"ForeignKeyConstraints",
|
|
70
|
+
"NotNullConstraint",
|
|
71
|
+
"RangeConstraint",
|
|
72
|
+
"RegexConstraint",
|
|
73
|
+
"ScriptedConstraint",
|
|
74
|
+
"UniqueConstraint",
|
|
75
|
+
"register_condition",
|
|
76
|
+
]
|