roadmap-cli 0.1.1__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.
- roadmap/__init__.py +27 -0
- roadmap/adapters/__init__.py +1 -0
- roadmap/adapters/base_paginated_adapter.py +117 -0
- roadmap/adapters/cli/__init__.py +249 -0
- roadmap/adapters/cli/analysis/__init__.py +5 -0
- roadmap/adapters/cli/analysis/commands.py +370 -0
- roadmap/adapters/cli/analysis/presenter.py +125 -0
- roadmap/adapters/cli/archive_operations.py +86 -0
- roadmap/adapters/cli/cli_command_helpers.py +109 -0
- roadmap/adapters/cli/cli_confirmations.py +74 -0
- roadmap/adapters/cli/cli_error_handlers.py +146 -0
- roadmap/adapters/cli/cli_validators.py +39 -0
- roadmap/adapters/cli/commands/sync_status.py +308 -0
- roadmap/adapters/cli/comment/__init__.py +5 -0
- roadmap/adapters/cli/comment/commands.py +106 -0
- roadmap/adapters/cli/config/__init__.py +5 -0
- roadmap/adapters/cli/config/commands.py +198 -0
- roadmap/adapters/cli/console_exports.py +10 -0
- roadmap/adapters/cli/core.py +26 -0
- roadmap/adapters/cli/crud/__init__.py +47 -0
- roadmap/adapters/cli/crud/base_archive.py +208 -0
- roadmap/adapters/cli/crud/base_create.py +154 -0
- roadmap/adapters/cli/crud/base_delete.py +155 -0
- roadmap/adapters/cli/crud/base_restore.py +283 -0
- roadmap/adapters/cli/crud/base_update.py +193 -0
- roadmap/adapters/cli/crud/crud_helpers.py +206 -0
- roadmap/adapters/cli/crud/crud_utils.py +152 -0
- roadmap/adapters/cli/crud/entity_builders.py +374 -0
- roadmap/adapters/cli/data/__init__.py +8 -0
- roadmap/adapters/cli/data/commands.py +149 -0
- roadmap/adapters/cli/decorators.py +208 -0
- roadmap/adapters/cli/dtos/__init__.py +155 -0
- roadmap/adapters/cli/exception_handler.py +105 -0
- roadmap/adapters/cli/git/__init__.py +8 -0
- roadmap/adapters/cli/git/commands.py +475 -0
- roadmap/adapters/cli/git/handlers/__init__.py +1 -0
- roadmap/adapters/cli/git/handlers/git_authentication_handler.py +124 -0
- roadmap/adapters/cli/git/handlers/git_branch_handler.py +238 -0
- roadmap/adapters/cli/git/handlers/git_connectivity_handler.py +122 -0
- roadmap/adapters/cli/git/handlers/git_hooks_handler.py +105 -0
- roadmap/adapters/cli/git/hooks_config.py +327 -0
- roadmap/adapters/cli/git/status_display.py +158 -0
- roadmap/adapters/cli/health/__init__.py +8 -0
- roadmap/adapters/cli/health/db_integrity.py +204 -0
- roadmap/adapters/cli/health/enhancer.py +227 -0
- roadmap/adapters/cli/health/fixer.py +295 -0
- roadmap/adapters/cli/health/fixers/__init__.py +10 -0
- roadmap/adapters/cli/health/fixers/corrupted_comments_fixer.py +231 -0
- roadmap/adapters/cli/health/fixers/data_integrity_fixer.py +137 -0
- roadmap/adapters/cli/health/fixers/duplicate_issues_fixer.py +138 -0
- roadmap/adapters/cli/health/fixers/folder_structure_fixer.py +114 -0
- roadmap/adapters/cli/health/fixers/label_normalization_fixer.py +162 -0
- roadmap/adapters/cli/health/fixers/milestone_name_normalization_fixer.py +181 -0
- roadmap/adapters/cli/health/fixers/milestone_naming_compliance_fixer.py +244 -0
- roadmap/adapters/cli/health/fixers/milestone_parse_error_fixer.py +162 -0
- roadmap/adapters/cli/health/fixers/milestone_validation_fixer.py +211 -0
- roadmap/adapters/cli/health/fixers/old_backups_fixer.py +135 -0
- roadmap/adapters/cli/health/fixers/orphaned_issues_fixer.py +252 -0
- roadmap/adapters/cli/health/formatter.py +250 -0
- roadmap/adapters/cli/health/formatters.py +427 -0
- roadmap/adapters/cli/health/scan.py +293 -0
- roadmap/adapters/cli/init/__init__.py +5 -0
- roadmap/adapters/cli/init/commands.py +587 -0
- roadmap/adapters/cli/init.py +9 -0
- roadmap/adapters/cli/issues/__init__.py +70 -0
- roadmap/adapters/cli/issues/archive.py +137 -0
- roadmap/adapters/cli/issues/archive_class.py +253 -0
- roadmap/adapters/cli/issues/block.py +39 -0
- roadmap/adapters/cli/issues/close.py +216 -0
- roadmap/adapters/cli/issues/comment.py +178 -0
- roadmap/adapters/cli/issues/create.py +162 -0
- roadmap/adapters/cli/issues/delete.py +31 -0
- roadmap/adapters/cli/issues/deps.py +161 -0
- roadmap/adapters/cli/issues/issue_status_helpers.py +111 -0
- roadmap/adapters/cli/issues/link.py +308 -0
- roadmap/adapters/cli/issues/list.py +402 -0
- roadmap/adapters/cli/issues/lookup.py +96 -0
- roadmap/adapters/cli/issues/progress.py +102 -0
- roadmap/adapters/cli/issues/restore.py +96 -0
- roadmap/adapters/cli/issues/restore_class.py +57 -0
- roadmap/adapters/cli/issues/start.py +165 -0
- roadmap/adapters/cli/issues/sync_status.py +374 -0
- roadmap/adapters/cli/issues/unblock.py +43 -0
- roadmap/adapters/cli/issues/unlink.py +104 -0
- roadmap/adapters/cli/issues/update.py +120 -0
- roadmap/adapters/cli/issues/view.py +36 -0
- roadmap/adapters/cli/layout.py +220 -0
- roadmap/adapters/cli/mappers.py +200 -0
- roadmap/adapters/cli/milestones/__init__.py +52 -0
- roadmap/adapters/cli/milestones/archive.py +125 -0
- roadmap/adapters/cli/milestones/archive_class.py +236 -0
- roadmap/adapters/cli/milestones/assign.py +62 -0
- roadmap/adapters/cli/milestones/close.py +177 -0
- roadmap/adapters/cli/milestones/create.py +131 -0
- roadmap/adapters/cli/milestones/delete.py +27 -0
- roadmap/adapters/cli/milestones/kanban.py +123 -0
- roadmap/adapters/cli/milestones/list.py +87 -0
- roadmap/adapters/cli/milestones/recalculate.py +79 -0
- roadmap/adapters/cli/milestones/restore.py +87 -0
- roadmap/adapters/cli/milestones/restore_class.py +99 -0
- roadmap/adapters/cli/milestones/update.py +61 -0
- roadmap/adapters/cli/milestones/view.py +142 -0
- roadmap/adapters/cli/output_manager.py +319 -0
- roadmap/adapters/cli/presentation/base_presenter.py +121 -0
- roadmap/adapters/cli/presentation/cleanup_presenter.py +235 -0
- roadmap/adapters/cli/presentation/core_initialization_presenter.py +277 -0
- roadmap/adapters/cli/presentation/crud_presenter.py +134 -0
- roadmap/adapters/cli/presentation/daily_summary_presenter.py +266 -0
- roadmap/adapters/cli/presentation/issue_presenter.py +258 -0
- roadmap/adapters/cli/presentation/milestone_list_presenter.py +135 -0
- roadmap/adapters/cli/presentation/milestone_presenter.py +297 -0
- roadmap/adapters/cli/presentation/project_presenter.py +233 -0
- roadmap/adapters/cli/presentation/project_status_presenter.py +186 -0
- roadmap/adapters/cli/presentation/table_builders.py +95 -0
- roadmap/adapters/cli/projects/__init__.py +42 -0
- roadmap/adapters/cli/projects/archive.py +127 -0
- roadmap/adapters/cli/projects/archive_class.py +96 -0
- roadmap/adapters/cli/projects/close.py +94 -0
- roadmap/adapters/cli/projects/create.py +50 -0
- roadmap/adapters/cli/projects/delete.py +27 -0
- roadmap/adapters/cli/projects/list.py +227 -0
- roadmap/adapters/cli/projects/restore.py +87 -0
- roadmap/adapters/cli/projects/restore_class.py +56 -0
- roadmap/adapters/cli/projects/update.py +57 -0
- roadmap/adapters/cli/projects/view.py +157 -0
- roadmap/adapters/cli/services/__init__.py +1 -0
- roadmap/adapters/cli/services/daily_summary_service.py +313 -0
- roadmap/adapters/cli/services/export_manager.py +268 -0
- roadmap/adapters/cli/services/milestone_list_service.py +266 -0
- roadmap/adapters/cli/services/project_initialization_service.py +29 -0
- roadmap/adapters/cli/services/project_status_service.py +462 -0
- roadmap/adapters/cli/services/sync_service.py +62 -0
- roadmap/adapters/cli/status.py +408 -0
- roadmap/adapters/cli/styling.py +21 -0
- roadmap/adapters/cli/sync.py +1117 -0
- roadmap/adapters/cli/sync_context.py +432 -0
- roadmap/adapters/cli/sync_handlers/__init__.py +77 -0
- roadmap/adapters/cli/sync_handlers/apply_ops.py +522 -0
- roadmap/adapters/cli/sync_handlers/baseline_ops.py +366 -0
- roadmap/adapters/cli/sync_handlers/conflict_ops.py +161 -0
- roadmap/adapters/cli/sync_handlers/dry_run_display.py +237 -0
- roadmap/adapters/cli/sync_handlers/interactive_resolver.py +396 -0
- roadmap/adapters/cli/sync_handlers/progress_tracker.py +128 -0
- roadmap/adapters/cli/sync_metrics_command.py +190 -0
- roadmap/adapters/cli/sync_presenter.py +58 -0
- roadmap/adapters/cli/sync_validation.py +295 -0
- roadmap/adapters/cli/today.py +52 -0
- roadmap/adapters/cli/utils/click_options.py +38 -0
- roadmap/adapters/git/__init__.py +1 -0
- roadmap/adapters/git/git.py +208 -0
- roadmap/adapters/git/git_branch_manager.py +400 -0
- roadmap/adapters/git/git_command_executor.py +56 -0
- roadmap/adapters/git/git_commit_analyzer.py +334 -0
- roadmap/adapters/git/git_hooks.py +22 -0
- roadmap/adapters/git/git_hooks_manager.py +455 -0
- roadmap/adapters/git/git_repository_info.py +65 -0
- roadmap/adapters/git/hook_installer.py +106 -0
- roadmap/adapters/git/hook_registry.py +97 -0
- roadmap/adapters/git/hook_script_generator.py +58 -0
- roadmap/adapters/git/sync_monitor.py +409 -0
- roadmap/adapters/git/workflow_automation.py +286 -0
- roadmap/adapters/github/__init__.py +1 -0
- roadmap/adapters/github/github.py +360 -0
- roadmap/adapters/github/handlers/__init__.py +17 -0
- roadmap/adapters/github/handlers/base.py +337 -0
- roadmap/adapters/github/handlers/collaborators.py +165 -0
- roadmap/adapters/github/handlers/comments.py +118 -0
- roadmap/adapters/github/handlers/issues.py +150 -0
- roadmap/adapters/github/handlers/labels.py +164 -0
- roadmap/adapters/github/handlers/milestones.py +104 -0
- roadmap/adapters/persistence/__init__.py +35 -0
- roadmap/adapters/persistence/conflict_resolver.py +190 -0
- roadmap/adapters/persistence/database_manager.py +492 -0
- roadmap/adapters/persistence/entity_sync_coordinators.py +672 -0
- roadmap/adapters/persistence/file_locking.py +348 -0
- roadmap/adapters/persistence/file_parser.py +92 -0
- roadmap/adapters/persistence/file_synchronizer.py +107 -0
- roadmap/adapters/persistence/focused_managers.py +140 -0
- roadmap/adapters/persistence/git_history.py +330 -0
- roadmap/adapters/persistence/parser/__init__.py +20 -0
- roadmap/adapters/persistence/parser/frontmatter.py +152 -0
- roadmap/adapters/persistence/parser/issue.py +245 -0
- roadmap/adapters/persistence/parser/milestone.py +146 -0
- roadmap/adapters/persistence/parser/project.py +106 -0
- roadmap/adapters/persistence/persistence.py +288 -0
- roadmap/adapters/persistence/repositories/__init__.py +15 -0
- roadmap/adapters/persistence/repositories/issue_repository.py +223 -0
- roadmap/adapters/persistence/repositories/milestone_repository.py +140 -0
- roadmap/adapters/persistence/repositories/project_repository.py +168 -0
- roadmap/adapters/persistence/repositories/remote_link_repository.py +355 -0
- roadmap/adapters/persistence/repositories/sync_state_repository.py +66 -0
- roadmap/adapters/persistence/storage/__init__.py +36 -0
- roadmap/adapters/persistence/storage/conflicts.py +101 -0
- roadmap/adapters/persistence/storage/connection_manager.py +79 -0
- roadmap/adapters/persistence/storage/issue_storage.py +89 -0
- roadmap/adapters/persistence/storage/milestone_storage.py +80 -0
- roadmap/adapters/persistence/storage/project_storage.py +102 -0
- roadmap/adapters/persistence/storage/queries.py +227 -0
- roadmap/adapters/persistence/storage/state_manager.py +652 -0
- roadmap/adapters/persistence/storage/sync_state_storage.py +207 -0
- roadmap/adapters/persistence/sync_metrics_repository.py +342 -0
- roadmap/adapters/persistence/sync_orchestrator.py +293 -0
- roadmap/adapters/persistence/sync_state_tracker.py +86 -0
- roadmap/adapters/persistence/yaml_repositories.py +711 -0
- roadmap/adapters/sync/__init__.py +21 -0
- roadmap/adapters/sync/backend_factory.py +108 -0
- roadmap/adapters/sync/backends/__init__.py +15 -0
- roadmap/adapters/sync/backends/converters.py +209 -0
- roadmap/adapters/sync/backends/github_backend_helpers.py +380 -0
- roadmap/adapters/sync/backends/github_client.py +99 -0
- roadmap/adapters/sync/backends/github_sync_backend.py +792 -0
- roadmap/adapters/sync/backends/github_sync_ops.py +908 -0
- roadmap/adapters/sync/backends/services/__init__.py +1 -0
- roadmap/adapters/sync/backends/services/github_authentication_service.py +105 -0
- roadmap/adapters/sync/backends/services/github_issue_delete_service.py +423 -0
- roadmap/adapters/sync/backends/services/github_issue_dependency_service.py +63 -0
- roadmap/adapters/sync/backends/services/github_issue_fetch_service.py +158 -0
- roadmap/adapters/sync/backends/services/github_label_sync_service.py +126 -0
- roadmap/adapters/sync/backends/services/github_local_issue_sync_service.py +229 -0
- roadmap/adapters/sync/backends/services/github_milestone_fetch_service.py +204 -0
- roadmap/adapters/sync/backends/vanilla_git_sync_backend.py +198 -0
- roadmap/adapters/sync/services/__init__.py +11 -0
- roadmap/adapters/sync/services/baseline_state_handler.py +83 -0
- roadmap/adapters/sync/services/conflict_converter.py +85 -0
- roadmap/adapters/sync/services/issue_persistence_service.py +263 -0
- roadmap/adapters/sync/services/issue_state_service.py +243 -0
- roadmap/adapters/sync/services/local_change_filter.py +129 -0
- roadmap/adapters/sync/services/pull_result_processor.py +71 -0
- roadmap/adapters/sync/services/remote_issue_creation_service.py +112 -0
- roadmap/adapters/sync/services/sync_analysis_service.py +250 -0
- roadmap/adapters/sync/services/sync_authentication_service.py +57 -0
- roadmap/adapters/sync/services/sync_data_fetch_service.py +212 -0
- roadmap/adapters/sync/services/sync_linking_service.py +329 -0
- roadmap/adapters/sync/services/sync_report_service.py +62 -0
- roadmap/adapters/sync/services/sync_state_update_service.py +51 -0
- roadmap/adapters/sync/sync_cache_orchestrator.py +454 -0
- roadmap/adapters/sync/sync_merge_engine.py +416 -0
- roadmap/adapters/sync/sync_merge_orchestrator.py +1313 -0
- roadmap/adapters/sync/sync_retrieval_orchestrator.py +810 -0
- roadmap/application/services/__init__.py +1 -0
- roadmap/application/services/deduplicate_service.py +517 -0
- roadmap/application/use_cases/__init__.py +1 -0
- roadmap/common/__init__.py +308 -0
- roadmap/common/cache.py +209 -0
- roadmap/common/cli_errors.py +147 -0
- roadmap/common/configuration/__init__.py +33 -0
- roadmap/common/configuration/config_loader.py +264 -0
- roadmap/common/configuration/config_manager.py +164 -0
- roadmap/common/configuration/config_schema.py +145 -0
- roadmap/common/configuration/github/__init__.py +9 -0
- roadmap/common/configuration/github/config_manager.py +154 -0
- roadmap/common/configuration/github/token_resolver.py +84 -0
- roadmap/common/console.py +184 -0
- roadmap/common/constants.py +134 -0
- roadmap/common/datetime_parser.py +319 -0
- roadmap/common/error_formatter.py +84 -0
- roadmap/common/errors/__init__.py +143 -0
- roadmap/common/errors/error_base.py +73 -0
- roadmap/common/errors/error_file.py +82 -0
- roadmap/common/errors/error_git.py +67 -0
- roadmap/common/errors/error_handler.py +166 -0
- roadmap/common/errors/error_network.py +90 -0
- roadmap/common/errors/error_security.py +78 -0
- roadmap/common/errors/error_standards.py +561 -0
- roadmap/common/errors/error_validation.py +111 -0
- roadmap/common/errors/exceptions.py +432 -0
- roadmap/common/formatters/__init__.py +29 -0
- roadmap/common/formatters/base_table_formatter.py +96 -0
- roadmap/common/formatters/export/__init__.py +7 -0
- roadmap/common/formatters/export/issue_exporter.py +97 -0
- roadmap/common/formatters/helpers.py +28 -0
- roadmap/common/formatters/kanban/__init__.py +9 -0
- roadmap/common/formatters/kanban/layout.py +48 -0
- roadmap/common/formatters/kanban/organizer.py +81 -0
- roadmap/common/formatters/output/__init__.py +15 -0
- roadmap/common/formatters/tables/__init__.py +11 -0
- roadmap/common/formatters/tables/column_factory.py +312 -0
- roadmap/common/formatters/tables/issue_table.py +276 -0
- roadmap/common/formatters/tables/milestone_table.py +247 -0
- roadmap/common/formatters/tables/project_table.py +195 -0
- roadmap/common/formatters/text/__init__.py +57 -0
- roadmap/common/formatters/text/basic.py +217 -0
- roadmap/common/formatters/text/display.py +42 -0
- roadmap/common/formatters/text/duration.py +36 -0
- roadmap/common/formatters/text/operations.py +325 -0
- roadmap/common/formatters/text/status_badges.py +57 -0
- roadmap/common/initialization/github/__init__.py +11 -0
- roadmap/common/initialization/github/setup_service.py +238 -0
- roadmap/common/initialization/github/setup_validator.py +78 -0
- roadmap/common/logging/__init__.py +113 -0
- roadmap/common/logging/audit_logging.py +5 -0
- roadmap/common/logging/correlation.py +45 -0
- roadmap/common/logging/decorators.py +267 -0
- roadmap/common/logging/error_logging.py +217 -0
- roadmap/common/logging/formatters.py +124 -0
- roadmap/common/logging/loggers.py +68 -0
- roadmap/common/logging/performance_tracking.py +267 -0
- roadmap/common/logging/utils.py +335 -0
- roadmap/common/models/__init__.py +44 -0
- roadmap/common/models/cli_models.py +118 -0
- roadmap/common/models/config_models.py +199 -0
- roadmap/common/models/output_models.py +381 -0
- roadmap/common/observability/__init__.py +28 -0
- roadmap/common/observability/instrumentation.py +103 -0
- roadmap/common/observability/observability.py +76 -0
- roadmap/common/observability/otel_init.py +109 -0
- roadmap/common/output_formatter.py +393 -0
- roadmap/common/progress.py +379 -0
- roadmap/common/result.py +396 -0
- roadmap/common/security/__init__.py +48 -0
- roadmap/common/security/exceptions.py +13 -0
- roadmap/common/security/export_cleanup.py +79 -0
- roadmap/common/security/file_operations.py +104 -0
- roadmap/common/security/filename_sanitization.py +54 -0
- roadmap/common/security/logging.py +27 -0
- roadmap/common/security/path_validation.py +137 -0
- roadmap/common/security/temp_files.py +41 -0
- roadmap/common/services/__init__.py +65 -0
- roadmap/common/services/decorators.py +183 -0
- roadmap/common/services/logging_utils.py +208 -0
- roadmap/common/services/metrics.py +161 -0
- roadmap/common/services/performance.py +219 -0
- roadmap/common/services/profiling.py +288 -0
- roadmap/common/services/retry.py +288 -0
- roadmap/common/status_style_manager.py +114 -0
- roadmap/common/union_find.py +88 -0
- roadmap/common/update_constants.py +13 -0
- roadmap/common/utils/__init__.py +65 -0
- roadmap/common/utils/cli_helpers.py +381 -0
- roadmap/common/utils/file_utils.py +440 -0
- roadmap/common/utils/path_utils.py +38 -0
- roadmap/common/utils/status_utils.py +130 -0
- roadmap/common/utils/timezone_utils.py +480 -0
- roadmap/common/validation/__init__.py +37 -0
- roadmap/common/validation/field_validator.py +104 -0
- roadmap/common/validation/result.py +37 -0
- roadmap/common/validation/roadmap_validator.py +310 -0
- roadmap/common/validation/schema_validator.py +36 -0
- roadmap/common/validation/validators.py +50 -0
- roadmap/core/__init__.py +1 -0
- roadmap/core/domain/__init__.py +30 -0
- roadmap/core/domain/comment.py +24 -0
- roadmap/core/domain/health.py +15 -0
- roadmap/core/domain/issue.py +274 -0
- roadmap/core/domain/milestone.py +173 -0
- roadmap/core/domain/ports/__init__.py +1 -0
- roadmap/core/domain/ports/baseline_repository.py +24 -0
- roadmap/core/domain/ports/issue_repository.py +34 -0
- roadmap/core/domain/ports/remote_backend_port.py +31 -0
- roadmap/core/domain/project.py +130 -0
- roadmap/core/interfaces/__init__.py +109 -0
- roadmap/core/interfaces/assignee_validator.py +40 -0
- roadmap/core/interfaces/backend_factory.py +175 -0
- roadmap/core/interfaces/github.py +102 -0
- roadmap/core/interfaces/parsers.py +140 -0
- roadmap/core/interfaces/persistence.py +45 -0
- roadmap/core/interfaces/repositories.py +174 -0
- roadmap/core/interfaces/state_managers.py +111 -0
- roadmap/core/interfaces/state_storage.py +64 -0
- roadmap/core/interfaces/sync_backend.py +275 -0
- roadmap/core/interfaces/sync_services.py +119 -0
- roadmap/core/interfaces/sync_validators.py +180 -0
- roadmap/core/models/__init__.py +31 -0
- roadmap/core/models/sync_models.py +162 -0
- roadmap/core/models.py +115 -0
- roadmap/core/observability/__init__.py +17 -0
- roadmap/core/observability/sync_metrics.py +699 -0
- roadmap/core/repositories.py +200 -0
- roadmap/core/services/__init__.py +313 -0
- roadmap/core/services/baseline/__init__.py +1 -0
- roadmap/core/services/baseline/baseline_builder_progress.py +274 -0
- roadmap/core/services/baseline/baseline_retriever.py +63 -0
- roadmap/core/services/baseline/baseline_selector.py +248 -0
- roadmap/core/services/baseline/baseline_state_retriever.py +284 -0
- roadmap/core/services/baseline/optimized_baseline_builder.py +425 -0
- roadmap/core/services/comment/__init__.py +1 -0
- roadmap/core/services/comment/comment_service.py +277 -0
- roadmap/core/services/git/__init__.py +1 -0
- roadmap/core/services/git/git_hook_auto_sync_service.py +403 -0
- roadmap/core/services/github/__init__.py +1 -0
- roadmap/core/services/github/github_change_detector.py +162 -0
- roadmap/core/services/github/github_config_validator.py +148 -0
- roadmap/core/services/github/github_conflict_detector.py +179 -0
- roadmap/core/services/github/github_entity_classifier.py +51 -0
- roadmap/core/services/github/github_integration_service.py +439 -0
- roadmap/core/services/github/github_issue_client.py +346 -0
- roadmap/core/services/health/__init__.py +1 -0
- roadmap/core/services/health/backup_cleanup_service.py +202 -0
- roadmap/core/services/health/data_integrity_validator_service.py +90 -0
- roadmap/core/services/health/entity_health_scanner.py +460 -0
- roadmap/core/services/health/file_repair_service.py +193 -0
- roadmap/core/services/health/health_check_service.py +166 -0
- roadmap/core/services/health/health_models.py +80 -0
- roadmap/core/services/health/infrastructure_validator_service.py +328 -0
- roadmap/core/services/health/issue_health_scanner.py +263 -0
- roadmap/core/services/helpers/__init__.py +18 -0
- roadmap/core/services/helpers/status_change_helpers.py +88 -0
- roadmap/core/services/initialization/__init__.py +19 -0
- roadmap/core/services/initialization/utils.py +104 -0
- roadmap/core/services/initialization/validator.py +78 -0
- roadmap/core/services/initialization/workflow.py +182 -0
- roadmap/core/services/initialization_service.py +111 -0
- roadmap/core/services/issue/__init__.py +1 -0
- roadmap/core/services/issue/assignee_validation_service.py +312 -0
- roadmap/core/services/issue/issue_creation_service.py +309 -0
- roadmap/core/services/issue/issue_filter_service.py +302 -0
- roadmap/core/services/issue/issue_matching_service.py +127 -0
- roadmap/core/services/issue/issue_service.py +765 -0
- roadmap/core/services/issue/issue_update_service.py +144 -0
- roadmap/core/services/issue/start_issue_service.py +174 -0
- roadmap/core/services/issue_helpers/__init__.py +18 -0
- roadmap/core/services/issue_helpers/issue_filters.py +296 -0
- roadmap/core/services/milestone_service.py +438 -0
- roadmap/core/services/project/__init__.py +1 -0
- roadmap/core/services/project/project_service.py +434 -0
- roadmap/core/services/project/project_status_service.py +125 -0
- roadmap/core/services/project_init/__init__.py +22 -0
- roadmap/core/services/project_init/context_detection.py +146 -0
- roadmap/core/services/project_init/creation.py +76 -0
- roadmap/core/services/project_init/detection.py +51 -0
- roadmap/core/services/project_init/template.py +228 -0
- roadmap/core/services/status_change_service.py +88 -0
- roadmap/core/services/sync/__init__.py +1 -0
- roadmap/core/services/sync/batch_processor.py +290 -0
- roadmap/core/services/sync/conflict_resolver.py +154 -0
- roadmap/core/services/sync/dependency_resolver.py +367 -0
- roadmap/core/services/sync/duplicate_detector.py +771 -0
- roadmap/core/services/sync/duplicate_resolver.py +315 -0
- roadmap/core/services/sync/error_classification.py +470 -0
- roadmap/core/services/sync/performance.py +263 -0
- roadmap/core/services/sync/sync_change_computer.py +211 -0
- roadmap/core/services/sync/sync_checkpoint.py +348 -0
- roadmap/core/services/sync/sync_conflict_detector.py +147 -0
- roadmap/core/services/sync/sync_conflict_resolver.py +448 -0
- roadmap/core/services/sync/sync_errors.py +383 -0
- roadmap/core/services/sync/sync_key_normalizer.py +222 -0
- roadmap/core/services/sync/sync_metadata_service.py +361 -0
- roadmap/core/services/sync/sync_plan.py +171 -0
- roadmap/core/services/sync/sync_plan_executor.py +447 -0
- roadmap/core/services/sync/sync_report.py +296 -0
- roadmap/core/services/sync/sync_state.py +173 -0
- roadmap/core/services/sync/sync_state_comparator.py +690 -0
- roadmap/core/services/sync/sync_state_manager.py +431 -0
- roadmap/core/services/sync/sync_state_normalizer.py +98 -0
- roadmap/core/services/sync/sync_three_way.py +78 -0
- roadmap/core/services/sync/three_way_merger.py +178 -0
- roadmap/core/services/utils/__init__.py +1 -0
- roadmap/core/services/utils/configuration_service.py +113 -0
- roadmap/core/services/utils/critical_path_calculator.py +418 -0
- roadmap/core/services/utils/dependency_analyzer.py +360 -0
- roadmap/core/services/utils/field_conflict_detector.py +126 -0
- roadmap/core/services/utils/remote_fetcher.py +202 -0
- roadmap/core/services/utils/remote_state_normalizer.py +110 -0
- roadmap/core/services/utils/retry_policy.py +51 -0
- roadmap/core/services/validator_base.py +130 -0
- roadmap/core/services/validators/__init__.py +28 -0
- roadmap/core/services/validators/_utils.py +20 -0
- roadmap/core/services/validators/archivable_issues_validator.py +71 -0
- roadmap/core/services/validators/archivable_milestones_validator.py +69 -0
- roadmap/core/services/validators/backup_validator.py +89 -0
- roadmap/core/services/validators/data_integrity_validator.py +84 -0
- roadmap/core/services/validators/duplicate_issues_validator.py +75 -0
- roadmap/core/services/validators/duplicate_milestones_validator.py +141 -0
- roadmap/core/services/validators/folder_structure_validator.py +165 -0
- roadmap/core/services/validators/health_status_utils.py +28 -0
- roadmap/core/services/validators/milestone_naming_validator.py +146 -0
- roadmap/core/services/validators/missing_headlines_validator.py +97 -0
- roadmap/core/services/validators/orphaned_issues_validator.py +142 -0
- roadmap/core/services/validators/orphaned_milestones_validator.py +85 -0
- roadmap/core/utils/git_remote_parser.py +83 -0
- roadmap/core/utils/github_urls.py +66 -0
- roadmap/domain/validation.py +30 -0
- roadmap/infrastructure/__init__.py +55 -0
- roadmap/infrastructure/coordination/__init__.py +8 -0
- roadmap/infrastructure/coordination/coordinator_params.py +82 -0
- roadmap/infrastructure/coordination/core.py +446 -0
- roadmap/infrastructure/coordination/git_coordinator.py +160 -0
- roadmap/infrastructure/coordination/initialization.py +348 -0
- roadmap/infrastructure/coordination/issue_coordinator.py +154 -0
- roadmap/infrastructure/coordination/issue_operations.py +456 -0
- roadmap/infrastructure/coordination/milestone_coordinator.py +135 -0
- roadmap/infrastructure/coordination/milestone_operations.py +201 -0
- roadmap/infrastructure/coordination/project_coordinator.py +113 -0
- roadmap/infrastructure/coordination/project_operations.py +144 -0
- roadmap/infrastructure/coordination/team_coordinator.py +107 -0
- roadmap/infrastructure/coordination/user_operations.py +150 -0
- roadmap/infrastructure/coordination/validation_coordinator.py +249 -0
- roadmap/infrastructure/coordination_gateway.py +152 -0
- roadmap/infrastructure/git/__init__.py +6 -0
- roadmap/infrastructure/git/git_integration_ops.py +234 -0
- roadmap/infrastructure/git_gateway.py +35 -0
- roadmap/infrastructure/github_gateway.py +52 -0
- roadmap/infrastructure/maintenance/__init__.py +5 -0
- roadmap/infrastructure/maintenance/cleanup.py +483 -0
- roadmap/infrastructure/observability/__init__.py +7 -0
- roadmap/infrastructure/observability/health.py +196 -0
- roadmap/infrastructure/observability/health_formatter.py +73 -0
- roadmap/infrastructure/observability/specialized_health_checkers.py +188 -0
- roadmap/infrastructure/persistence_gateway.py +164 -0
- roadmap/infrastructure/security/__init__.py +1 -0
- roadmap/infrastructure/security/credentials.py +489 -0
- roadmap/infrastructure/sync_gateway.py +94 -0
- roadmap/infrastructure/validation/__init__.py +7 -0
- roadmap/infrastructure/validation/file_enumeration.py +219 -0
- roadmap/infrastructure/validation/github_assignee_validator.py +47 -0
- roadmap/infrastructure/validation/github_validator.py +101 -0
- roadmap/infrastructure/validation/milestone_consistency_validator.py +96 -0
- roadmap/infrastructure/validation/vanilla_assignee_validator.py +37 -0
- roadmap/infrastructure/validation_gateway.py +96 -0
- roadmap/presentation/formatters/sync_metrics_formatter.py +355 -0
- roadmap/settings.py +345 -0
- roadmap/templates/github_workflows/ci-cd-roadmap.yml +241 -0
- roadmap/templates/github_workflows/issue-lifecycle.yml +236 -0
- roadmap/templates/github_workflows/roadmap-integration.yml +137 -0
- roadmap/templates/github_workflows/roadmap-starter.yml +59 -0
- roadmap/version.py +381 -0
- roadmap_cli-0.1.1.dist-info/METADATA +448 -0
- roadmap_cli-0.1.1.dist-info/RECORD +521 -0
- roadmap_cli-0.1.1.dist-info/WHEEL +4 -0
- roadmap_cli-0.1.1.dist-info/entry_points.txt +2 -0
- roadmap_cli-0.1.1.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"""Standardized error handling patterns and decorators.
|
|
2
|
+
|
|
3
|
+
This module provides reusable decorators and context managers for consistent
|
|
4
|
+
error handling across the codebase. All decorators automatically log errors
|
|
5
|
+
with rich context, support retry logic for transient failures, and convert
|
|
6
|
+
exceptions to appropriate RoadmapException types.
|
|
7
|
+
|
|
8
|
+
Key patterns:
|
|
9
|
+
@safe_operation - Wraps CRUD operations with automatic error handling
|
|
10
|
+
@log_operation - Lightweight operation logging without exception conversion
|
|
11
|
+
with_error_handling() - Context manager for error handling blocks
|
|
12
|
+
ErrorContext - Builder for rich error context at error time
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
@safe_operation(OperationType.CREATE, "Issue")
|
|
16
|
+
def create_issue(title: str) -> Issue:
|
|
17
|
+
return Issue(title) # Errors automatically caught, logged, wrapped
|
|
18
|
+
|
|
19
|
+
@safe_operation(OperationType.SYNC, "GitHub", retryable=True, max_retries=3)
|
|
20
|
+
def fetch_issues(project_id: str) -> list[dict]:
|
|
21
|
+
return github_api.get_issues(project_id) # Retries on transient failures
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import time
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from contextlib import contextmanager
|
|
27
|
+
from functools import wraps
|
|
28
|
+
from typing import Any, ParamSpec, TypeVar
|
|
29
|
+
|
|
30
|
+
from roadmap.common.logging import get_logger
|
|
31
|
+
from roadmap.common.logging.error_logging import (
|
|
32
|
+
is_error_recoverable,
|
|
33
|
+
log_error_with_context,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
logger = get_logger(__name__)
|
|
37
|
+
|
|
38
|
+
P = ParamSpec("P")
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ============================================================================
|
|
43
|
+
# STANDARD OPERATION TYPES
|
|
44
|
+
# ============================================================================
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OperationType:
|
|
48
|
+
"""Standard operation type constants for consistent error handling."""
|
|
49
|
+
|
|
50
|
+
CREATE = "create"
|
|
51
|
+
READ = "read"
|
|
52
|
+
UPDATE = "update"
|
|
53
|
+
DELETE = "delete"
|
|
54
|
+
SYNC = "sync"
|
|
55
|
+
IMPORT = "import"
|
|
56
|
+
EXPORT = "export"
|
|
57
|
+
VALIDATE = "validate"
|
|
58
|
+
AUTHENTICATE = "authenticate"
|
|
59
|
+
FETCH = "fetch"
|
|
60
|
+
SAVE = "save"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ============================================================================
|
|
64
|
+
# ERROR CONTEXT BUILDER - Captures rich context at error time
|
|
65
|
+
# ============================================================================
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ErrorContext:
|
|
69
|
+
"""Builder for rich error context to include in logs and exceptions.
|
|
70
|
+
|
|
71
|
+
Fluent API for capturing operation context that will be included in
|
|
72
|
+
error logs and exception messages. Supports context like entity IDs,
|
|
73
|
+
input parameters, operation state, and suggested recovery actions.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
context = (
|
|
77
|
+
ErrorContext("sync", "Issues")
|
|
78
|
+
.with_entity_id(project_id)
|
|
79
|
+
.with_input(force=True, filters={})
|
|
80
|
+
.with_state(synced_count=10, failed_count=1)
|
|
81
|
+
.with_recovery("retry", "Check network connection")
|
|
82
|
+
)
|
|
83
|
+
context_dict = context.build()
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, operation: str, entity_type: str | None = None):
|
|
87
|
+
"""Initialize error context.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
operation: Name of the operation (e.g., "create", "sync")
|
|
91
|
+
entity_type: Type of entity being operated on (e.g., "Issue", "Milestone")
|
|
92
|
+
"""
|
|
93
|
+
self.data: dict[str, Any] = {
|
|
94
|
+
"operation": operation,
|
|
95
|
+
}
|
|
96
|
+
if entity_type:
|
|
97
|
+
self.data["entity_type"] = entity_type
|
|
98
|
+
|
|
99
|
+
def with_entity_id(self, entity_id: str | int) -> "ErrorContext":
|
|
100
|
+
"""Add entity ID to context."""
|
|
101
|
+
self.data["entity_id"] = str(entity_id)
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
def with_input(self, **kwargs: Any) -> "ErrorContext":
|
|
105
|
+
"""Add input parameters to context."""
|
|
106
|
+
if kwargs:
|
|
107
|
+
self.data["input"] = kwargs
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def with_state(self, **kwargs: Any) -> "ErrorContext":
|
|
111
|
+
"""Add operation state to context."""
|
|
112
|
+
if kwargs:
|
|
113
|
+
self.data["state"] = kwargs
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def with_recovery(self, action: str, details: str = "") -> "ErrorContext":
|
|
117
|
+
"""Add recovery suggestion to context."""
|
|
118
|
+
self.data["recovery_action"] = action
|
|
119
|
+
if details:
|
|
120
|
+
self.data["recovery_details"] = details
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def with_attempt(self, attempt: int, max_retries: int) -> "ErrorContext":
|
|
124
|
+
"""Add retry attempt information to context."""
|
|
125
|
+
self.data["retry_attempt"] = attempt
|
|
126
|
+
self.data["max_retries"] = max_retries
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def build(self) -> dict[str, Any]:
|
|
130
|
+
"""Build and return the context dictionary."""
|
|
131
|
+
return self.data.copy()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ============================================================================
|
|
135
|
+
# DECORATOR: @safe_operation - Wraps CRUD operations with standard handling
|
|
136
|
+
# ============================================================================
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def safe_operation(
|
|
140
|
+
operation_type: str,
|
|
141
|
+
entity_type: str | None = None,
|
|
142
|
+
include_traceback: bool = False,
|
|
143
|
+
retryable: bool = False,
|
|
144
|
+
max_retries: int = 1,
|
|
145
|
+
retry_delay: float = 0.5,
|
|
146
|
+
retry_backoff: float = 2.0,
|
|
147
|
+
) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
|
148
|
+
"""Execute operation safely with standard error handling.
|
|
149
|
+
|
|
150
|
+
Wraps create, read, update, delete, sync operations with:
|
|
151
|
+
- Automatic error classification and logging with rich context
|
|
152
|
+
- Graceful exception conversion to appropriate RoadmapException
|
|
153
|
+
- Optional retry on recoverable errors with exponential backoff
|
|
154
|
+
- Traceback inclusion for debugging when needed
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
operation_type: Type of operation (OperationType constants)
|
|
158
|
+
entity_type: Type of entity being operated on (Issue, Milestone, etc.)
|
|
159
|
+
include_traceback: Whether to include full traceback in error logs
|
|
160
|
+
retryable: Whether to retry on recoverable errors
|
|
161
|
+
max_retries: Maximum retry attempts (if retryable=True)
|
|
162
|
+
retry_delay: Initial delay in seconds between retries
|
|
163
|
+
retry_backoff: Multiplier for delay after each retry (exponential backoff)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Decorator function
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
@safe_operation(OperationType.CREATE, "Issue")
|
|
170
|
+
def create_issue(title: str, description: str) -> Issue:
|
|
171
|
+
# Function body - errors are automatically caught and logged
|
|
172
|
+
return Issue(title, description)
|
|
173
|
+
|
|
174
|
+
@safe_operation(OperationType.DELETE, "Issue", retryable=True, max_retries=3)
|
|
175
|
+
def delete_issue(issue_id: str) -> None:
|
|
176
|
+
# Will retry up to 3 times on network/timeout errors
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
CreateError: If operation_type is CREATE and original error occurs
|
|
181
|
+
UpdateError: If operation_type is UPDATE and original error occurs
|
|
182
|
+
DeleteError: If operation_type is DELETE and original error occurs
|
|
183
|
+
RoadmapException: For other operation types
|
|
184
|
+
"""
|
|
185
|
+
from roadmap.common.errors.exceptions import (
|
|
186
|
+
CreateError,
|
|
187
|
+
DeleteError,
|
|
188
|
+
RoadmapException,
|
|
189
|
+
UpdateError,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def decorator(func: Callable[P, T]) -> Callable[P, T]:
|
|
193
|
+
@wraps(func)
|
|
194
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: # type: ignore[return-value]
|
|
195
|
+
context = ErrorContext(operation_type, entity_type)
|
|
196
|
+
|
|
197
|
+
# Capture first string/int argument as entity_id if available
|
|
198
|
+
if args and isinstance(args[0], str | int):
|
|
199
|
+
context.with_entity_id(args[0])
|
|
200
|
+
elif "id" in kwargs and isinstance(kwargs["id"], str | int):
|
|
201
|
+
context.with_entity_id(kwargs["id"])
|
|
202
|
+
|
|
203
|
+
# Capture input parameters for debugging
|
|
204
|
+
context.with_input(**kwargs)
|
|
205
|
+
|
|
206
|
+
attempt = 0
|
|
207
|
+
last_error: Exception | None = None
|
|
208
|
+
delay = retry_delay
|
|
209
|
+
|
|
210
|
+
while attempt < max_retries:
|
|
211
|
+
try:
|
|
212
|
+
if attempt > 0:
|
|
213
|
+
logger.debug(
|
|
214
|
+
"retry_attempt",
|
|
215
|
+
attempt=attempt,
|
|
216
|
+
max_retries=max_retries,
|
|
217
|
+
entity_type=entity_type,
|
|
218
|
+
operation_type=operation_type,
|
|
219
|
+
)
|
|
220
|
+
return func(*args, **kwargs)
|
|
221
|
+
|
|
222
|
+
except RoadmapException:
|
|
223
|
+
# Already a roadmap exception - let it propagate
|
|
224
|
+
raise
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
# Lazy import to avoid infrastructure dependency in common layer
|
|
228
|
+
from roadmap.common.logging.error_logging import (
|
|
229
|
+
is_error_recoverable,
|
|
230
|
+
log_error_with_context,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
last_error = e
|
|
234
|
+
is_recoverable = is_error_recoverable(e)
|
|
235
|
+
|
|
236
|
+
# Build context with retry information if applicable
|
|
237
|
+
error_context = context.build()
|
|
238
|
+
if retryable and max_retries > 1:
|
|
239
|
+
error_context["retry_attempt"] = attempt + 1
|
|
240
|
+
error_context["max_retries"] = max_retries
|
|
241
|
+
|
|
242
|
+
# Log the error with full context
|
|
243
|
+
log_error_with_context(
|
|
244
|
+
e,
|
|
245
|
+
f"{operation_type}_{entity_type or 'unknown'}",
|
|
246
|
+
entity_type=entity_type,
|
|
247
|
+
additional_context=error_context,
|
|
248
|
+
include_traceback=include_traceback,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Check if we should retry
|
|
252
|
+
if retryable and is_recoverable and attempt < max_retries - 1:
|
|
253
|
+
attempt += 1
|
|
254
|
+
logger.info(
|
|
255
|
+
"retry_in_progress",
|
|
256
|
+
attempt=attempt,
|
|
257
|
+
max_retries=max_retries,
|
|
258
|
+
error_type=type(e).__name__,
|
|
259
|
+
delay_seconds=delay,
|
|
260
|
+
operation_type=operation_type,
|
|
261
|
+
)
|
|
262
|
+
time.sleep(delay)
|
|
263
|
+
delay *= retry_backoff
|
|
264
|
+
continue # Retry
|
|
265
|
+
|
|
266
|
+
# Convert to appropriate RoadmapException if not retrying
|
|
267
|
+
attempt_info = (
|
|
268
|
+
f" (after {attempt} retries)"
|
|
269
|
+
if retryable and attempt > 0
|
|
270
|
+
else ""
|
|
271
|
+
)
|
|
272
|
+
error_msg = f"{str(e)}{attempt_info}"
|
|
273
|
+
|
|
274
|
+
if operation_type == OperationType.CREATE:
|
|
275
|
+
raise CreateError(
|
|
276
|
+
entity_type or "entity",
|
|
277
|
+
error_msg,
|
|
278
|
+
) from e
|
|
279
|
+
elif operation_type == OperationType.UPDATE:
|
|
280
|
+
raise UpdateError(
|
|
281
|
+
entity_type or "entity",
|
|
282
|
+
error_msg,
|
|
283
|
+
) from e
|
|
284
|
+
elif operation_type == OperationType.DELETE:
|
|
285
|
+
raise DeleteError(
|
|
286
|
+
entity_type or "entity",
|
|
287
|
+
error_msg,
|
|
288
|
+
) from e
|
|
289
|
+
else:
|
|
290
|
+
raise RoadmapException(
|
|
291
|
+
domain_message=f"{operation_type} failed: {error_msg}",
|
|
292
|
+
user_message=f"Failed to {operation_type}: {str(e)}",
|
|
293
|
+
) from e
|
|
294
|
+
|
|
295
|
+
# If we exhausted retries, raise the last error
|
|
296
|
+
if last_error:
|
|
297
|
+
raise last_error
|
|
298
|
+
|
|
299
|
+
return wrapper
|
|
300
|
+
|
|
301
|
+
return decorator
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# ============================================================================
|
|
305
|
+
# DECORATOR: @log_operation - For operations that don't need exception handling
|
|
306
|
+
# ============================================================================
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def log_operation(
|
|
310
|
+
operation_type: str,
|
|
311
|
+
entity_type: str | None = None,
|
|
312
|
+
log_inputs: bool = True,
|
|
313
|
+
log_output: bool = False,
|
|
314
|
+
) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
|
315
|
+
"""Lightweight decorator to log operation execution without error handling.
|
|
316
|
+
|
|
317
|
+
Use for operations that are already well-protected or don't need automatic
|
|
318
|
+
error conversion. Logs the operation, inputs, and optionally outputs.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
operation_type: Type of operation
|
|
322
|
+
entity_type: Type of entity
|
|
323
|
+
log_inputs: Whether to log input arguments
|
|
324
|
+
log_output: Whether to log the return value
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Decorator function
|
|
328
|
+
|
|
329
|
+
Example:
|
|
330
|
+
@log_operation(OperationType.READ, "Issue", log_inputs=True)
|
|
331
|
+
def get_issue(issue_id: str) -> Issue:
|
|
332
|
+
# Log entry/exit but let exceptions propagate normally
|
|
333
|
+
return issues_db.get(issue_id)
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def decorator(func: Callable[P, T]) -> Callable[P, T]:
|
|
337
|
+
@wraps(func)
|
|
338
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
339
|
+
if log_inputs:
|
|
340
|
+
logger.info(
|
|
341
|
+
"operation_started",
|
|
342
|
+
operation_type=operation_type,
|
|
343
|
+
entity_type=entity_type,
|
|
344
|
+
**kwargs,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
result = func(*args, **kwargs)
|
|
348
|
+
|
|
349
|
+
if log_output and result:
|
|
350
|
+
logger.info(
|
|
351
|
+
"operation_completed",
|
|
352
|
+
operation_type=operation_type,
|
|
353
|
+
entity_type=entity_type,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return result
|
|
357
|
+
|
|
358
|
+
return wrapper
|
|
359
|
+
|
|
360
|
+
return decorator
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# ============================================================================
|
|
364
|
+
# CONTEXT MANAGER: with_error_handling - For non-function error handling
|
|
365
|
+
# ============================================================================
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@contextmanager
|
|
369
|
+
def with_error_handling(
|
|
370
|
+
operation: str,
|
|
371
|
+
entity_type: str | None = None,
|
|
372
|
+
entity_id: str | int | None = None,
|
|
373
|
+
fail_silently: bool = False,
|
|
374
|
+
include_traceback: bool = False,
|
|
375
|
+
):
|
|
376
|
+
"""Context manager for error handling in operation blocks.
|
|
377
|
+
|
|
378
|
+
Use when you have a block of code that might fail and you want consistent
|
|
379
|
+
error logging and exception wrapping without using a decorator.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
operation: Name of the operation (e.g., "sync", "export")
|
|
383
|
+
entity_type: Type of entity involved
|
|
384
|
+
entity_id: ID of the entity being operated on
|
|
385
|
+
fail_silently: If True, log error but don't re-raise
|
|
386
|
+
include_traceback: Include full traceback in logs
|
|
387
|
+
|
|
388
|
+
Yields:
|
|
389
|
+
None
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
RoadmapException: If error occurs and fail_silently=False
|
|
393
|
+
|
|
394
|
+
Example:
|
|
395
|
+
with with_error_handling("sync", "Issues", entity_id=project_id):
|
|
396
|
+
# Code that might fail
|
|
397
|
+
sync_issues(project_id)
|
|
398
|
+
|
|
399
|
+
# Error is automatically logged and wrapped in appropriate exception
|
|
400
|
+
"""
|
|
401
|
+
from roadmap.common.errors.exceptions import RoadmapException
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
yield
|
|
405
|
+
except RoadmapException:
|
|
406
|
+
raise
|
|
407
|
+
except Exception as e:
|
|
408
|
+
context = {
|
|
409
|
+
"operation": operation,
|
|
410
|
+
"entity_type": entity_type,
|
|
411
|
+
"entity_id": str(entity_id) if entity_id else None,
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
log_error_with_context(
|
|
415
|
+
e,
|
|
416
|
+
operation,
|
|
417
|
+
entity_type=entity_type,
|
|
418
|
+
entity_id=str(entity_id) if entity_id else None,
|
|
419
|
+
additional_context=context,
|
|
420
|
+
include_traceback=include_traceback,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
if not fail_silently:
|
|
424
|
+
raise RoadmapException(
|
|
425
|
+
domain_message=f"{operation} failed: {str(e)}",
|
|
426
|
+
user_message=f"Failed to {operation}",
|
|
427
|
+
) from e
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# ============================================================================
|
|
431
|
+
# RECOVERY ACTION HANDLERS - For application-level recovery
|
|
432
|
+
# ============================================================================
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class RecoveryAction:
|
|
436
|
+
"""Handles recovery from common error scenarios.
|
|
437
|
+
|
|
438
|
+
Provides static methods for common recovery patterns like retry with
|
|
439
|
+
backoff, handling missing files, and suggesting recovery actions.
|
|
440
|
+
|
|
441
|
+
Example:
|
|
442
|
+
try:
|
|
443
|
+
result = risky_operation()
|
|
444
|
+
except ConnectionError as e:
|
|
445
|
+
if RecoveryAction.is_retryable(e):
|
|
446
|
+
result = RecoveryAction.retry_with_backoff(risky_operation)
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def retry_with_backoff(
|
|
451
|
+
func: Callable[..., T],
|
|
452
|
+
max_attempts: int = 3,
|
|
453
|
+
initial_delay: float = 0.5,
|
|
454
|
+
backoff_factor: float = 2.0,
|
|
455
|
+
) -> T: # type: ignore[return-value]
|
|
456
|
+
"""Retry a function with exponential backoff.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
func: Function to retry
|
|
460
|
+
max_attempts: Maximum number of attempts
|
|
461
|
+
initial_delay: Initial delay in seconds between retries
|
|
462
|
+
backoff_factor: Multiplier for delay after each retry
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Return value from successful function call
|
|
466
|
+
|
|
467
|
+
Raises:
|
|
468
|
+
Last exception if all retries exhausted
|
|
469
|
+
"""
|
|
470
|
+
last_error: Exception | None = None
|
|
471
|
+
delay = initial_delay
|
|
472
|
+
|
|
473
|
+
for attempt in range(max_attempts):
|
|
474
|
+
try:
|
|
475
|
+
return func()
|
|
476
|
+
except Exception as e:
|
|
477
|
+
last_error = e
|
|
478
|
+
if attempt < max_attempts - 1 and is_error_recoverable(e):
|
|
479
|
+
time.sleep(delay)
|
|
480
|
+
delay *= backoff_factor
|
|
481
|
+
else:
|
|
482
|
+
raise
|
|
483
|
+
|
|
484
|
+
if last_error:
|
|
485
|
+
raise last_error
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def is_retryable(error: Exception) -> bool:
|
|
489
|
+
"""Check if an error is retryable.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
error: Exception to check
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
True if error is recoverable/retryable
|
|
496
|
+
"""
|
|
497
|
+
return is_error_recoverable(error)
|
|
498
|
+
|
|
499
|
+
@staticmethod
|
|
500
|
+
def handle_missing_file(
|
|
501
|
+
filepath: str, create_default: bool = True, content: str = ""
|
|
502
|
+
) -> bool:
|
|
503
|
+
"""Handle missing file error by creating default.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
filepath: Path to the missing file
|
|
507
|
+
create_default: Whether to create a default file
|
|
508
|
+
content: Content to write to the file
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
True if file was created successfully, False otherwise
|
|
512
|
+
"""
|
|
513
|
+
if not create_default:
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
from pathlib import Path
|
|
517
|
+
|
|
518
|
+
try:
|
|
519
|
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
|
520
|
+
Path(filepath).write_text(content)
|
|
521
|
+
logger.info("created_missing_file", filepath=filepath)
|
|
522
|
+
return True
|
|
523
|
+
except Exception as e:
|
|
524
|
+
logger.error("failed_to_create_file", filepath=filepath, error=str(e))
|
|
525
|
+
return False
|
|
526
|
+
|
|
527
|
+
@staticmethod
|
|
528
|
+
def handle_permission_error(filepath: str) -> str:
|
|
529
|
+
"""Suggest recovery action for permission error.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
filepath: Path that had permission error
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Suggested recovery command
|
|
536
|
+
"""
|
|
537
|
+
return f"Check file permissions: chmod u+rw {filepath}"
|
|
538
|
+
|
|
539
|
+
@staticmethod
|
|
540
|
+
def handle_connection_error(service: str) -> str:
|
|
541
|
+
"""Suggest recovery action for connection error.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
service: Name of service that couldn't be reached
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Suggested recovery action
|
|
548
|
+
"""
|
|
549
|
+
return (
|
|
550
|
+
f"Unable to reach {service}. Check network connection and service status."
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
__all__ = [
|
|
555
|
+
"OperationType",
|
|
556
|
+
"ErrorContext",
|
|
557
|
+
"safe_operation",
|
|
558
|
+
"log_operation",
|
|
559
|
+
"with_error_handling",
|
|
560
|
+
"RecoveryAction",
|
|
561
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Validation related error classes."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from roadmap.common.errors.error_base import ErrorCategory, ErrorSeverity, RoadmapError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ValidationError(RoadmapError):
|
|
9
|
+
"""Errors related to data validation."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self, message: str, field: str | None = None, value: Any | None = None, **kwargs
|
|
13
|
+
):
|
|
14
|
+
"""Initialize ValidationError.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
message: Error message.
|
|
18
|
+
field: Name of the field that failed validation.
|
|
19
|
+
value: Value that failed validation.
|
|
20
|
+
**kwargs: Additional arguments passed to parent class.
|
|
21
|
+
"""
|
|
22
|
+
context = kwargs.get("context", {})
|
|
23
|
+
if field:
|
|
24
|
+
context["field"] = field
|
|
25
|
+
if value is not None:
|
|
26
|
+
context["value"] = str(value)
|
|
27
|
+
|
|
28
|
+
super().__init__(
|
|
29
|
+
message,
|
|
30
|
+
severity=kwargs.get("severity", ErrorSeverity.MEDIUM),
|
|
31
|
+
category=ErrorCategory.VALIDATION,
|
|
32
|
+
context=context,
|
|
33
|
+
cause=kwargs.get("cause"),
|
|
34
|
+
)
|
|
35
|
+
self.field = field
|
|
36
|
+
self.value = value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StateError(RoadmapError):
|
|
40
|
+
"""Raised when operation is invalid for current state."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, message: str, current_state: str | None = None, **kwargs):
|
|
43
|
+
"""Initialize StateError.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
message: Error message.
|
|
47
|
+
current_state: Current state that made the operation invalid.
|
|
48
|
+
**kwargs: Additional arguments passed to parent class.
|
|
49
|
+
"""
|
|
50
|
+
context = kwargs.get("context", {})
|
|
51
|
+
if current_state:
|
|
52
|
+
context["current_state"] = current_state
|
|
53
|
+
|
|
54
|
+
super().__init__(
|
|
55
|
+
message,
|
|
56
|
+
severity=kwargs.get("severity", ErrorSeverity.MEDIUM),
|
|
57
|
+
category=ErrorCategory.VALIDATION,
|
|
58
|
+
context=context,
|
|
59
|
+
cause=kwargs.get("cause"),
|
|
60
|
+
)
|
|
61
|
+
self.current_state = current_state
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class IssueNotFoundError(RoadmapError):
|
|
65
|
+
"""Raised when an issue cannot be found."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, message: str, issue_id: str | None = None, **kwargs):
|
|
68
|
+
"""Initialize IssueNotFoundError.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
message: Error message.
|
|
72
|
+
issue_id: ID of the issue that was not found.
|
|
73
|
+
**kwargs: Additional arguments passed to parent class.
|
|
74
|
+
"""
|
|
75
|
+
context = kwargs.get("context", {})
|
|
76
|
+
if issue_id:
|
|
77
|
+
context["issue_id"] = issue_id
|
|
78
|
+
|
|
79
|
+
super().__init__(
|
|
80
|
+
message,
|
|
81
|
+
severity=kwargs.get("severity", ErrorSeverity.MEDIUM),
|
|
82
|
+
category=ErrorCategory.VALIDATION,
|
|
83
|
+
context=context,
|
|
84
|
+
cause=kwargs.get("cause"),
|
|
85
|
+
)
|
|
86
|
+
self.issue_id = issue_id
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class MilestoneNotFoundError(RoadmapError):
|
|
90
|
+
"""Raised when a milestone cannot be found."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, message: str, milestone_name: str | None = None, **kwargs):
|
|
93
|
+
"""Initialize MilestoneNotFoundError.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
message: Error message.
|
|
97
|
+
milestone_name: Name of the milestone that was not found.
|
|
98
|
+
**kwargs: Additional arguments passed to parent class.
|
|
99
|
+
"""
|
|
100
|
+
context = kwargs.get("context", {})
|
|
101
|
+
if milestone_name:
|
|
102
|
+
context["milestone"] = milestone_name
|
|
103
|
+
|
|
104
|
+
super().__init__(
|
|
105
|
+
message,
|
|
106
|
+
severity=kwargs.get("severity", ErrorSeverity.MEDIUM),
|
|
107
|
+
category=ErrorCategory.VALIDATION,
|
|
108
|
+
context=context,
|
|
109
|
+
cause=kwargs.get("cause"),
|
|
110
|
+
)
|
|
111
|
+
self.milestone_name = milestone_name
|