dpone 0.1.0__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.
- dpone/.env.example +70 -0
- dpone/.gitignore +0 -0
- dpone/__init__.py +61 -0
- dpone/adapters/__init__.py +0 -0
- dpone/adapters/fs_local.py +21 -0
- dpone/adapters/fs_memory.py +30 -0
- dpone/adapters/yaml_pyyaml.py +16 -0
- dpone/app/__init__.py +0 -0
- dpone/app/cli_reference_source.py +13 -0
- dpone/app/context.py +33 -0
- dpone/app/logging.py +23 -0
- dpone/app/settings.py +68 -0
- dpone/cli/__init__.py +7 -0
- dpone/cli/legacy.py +74 -0
- dpone/cli/main.py +42 -0
- dpone/cli/parser.py +15 -0
- dpone/cli_render/__init__.py +12 -0
- dpone/cli_render/dag/__init__.py +3 -0
- dpone/cli_render/dag/common.py +144 -0
- dpone/cli_render/dag/deps.py +70 -0
- dpone/cli_render/dag/e2e_attribution.py +160 -0
- dpone/cli_render/dag/edge.py +95 -0
- dpone/cli_render/dag/edge_e2e.py +133 -0
- dpone/cli_render/dag/node.py +104 -0
- dpone/cli_render/dag/node_e2e.py +134 -0
- dpone/cli_render/dag/report.py +55 -0
- dpone/cli_render/manifest/__init__.py +19 -0
- dpone/cli_render/manifest/common.py +114 -0
- dpone/cli_render/manifest/explain.py +273 -0
- dpone/cli_render/manifest/list.py +11 -0
- dpone/cli_render/manifest/migrate.py +15 -0
- dpone/cli_render/manifest/registry_lint.py +15 -0
- dpone/cli_render/manifest/render.py +13 -0
- dpone/cli_render/manifest/stats.py +23 -0
- dpone/cli_render/manifest/validate.py +13 -0
- dpone/cli_render/manifest/verify.py +20 -0
- dpone/commands/__init__.py +0 -0
- dpone/commands/base.py +29 -0
- dpone/commands/dag/__init__.py +0 -0
- dpone/commands/dag/explain_deps_cmd.py +120 -0
- dpone/commands/dag/explain_edge_cmd.py +123 -0
- dpone/commands/dag/explain_edge_e2e_cmd.py +134 -0
- dpone/commands/dag/explain_node_cmd.py +93 -0
- dpone/commands/dag/explain_node_e2e_cmd.py +114 -0
- dpone/commands/dag/list_edges_cmd.py +309 -0
- dpone/commands/dag/report_cmd.py +255 -0
- dpone/commands/dag/subgraph_cmd.py +213 -0
- dpone/commands/docs/__init__.py +60 -0
- dpone/commands/docs/check_compatibility_cmd.py +52 -0
- dpone/commands/docs/check_docs_cmd.py +44 -0
- dpone/commands/docs/check_import_rules_cmd.py +37 -0
- dpone/commands/docs/check_layer_metrics_cmd.py +97 -0
- dpone/commands/docs/check_removal_readiness_cmd.py +36 -0
- dpone/commands/docs/update_cli_reference_cmd.py +26 -0
- dpone/commands/docs/update_deprecation_roadmap_cmd.py +35 -0
- dpone/commands/docs/update_dev_metrics_cmd.py +45 -0
- dpone/commands/docs/update_shim_removal_plan_cmd.py +29 -0
- dpone/commands/func_command.py +62 -0
- dpone/commands/manifest/__init__.py +0 -0
- dpone/commands/manifest/explain_cmd.py +145 -0
- dpone/commands/manifest/lint_registry_cmd.py +64 -0
- dpone/commands/manifest/list_cmd.py +65 -0
- dpone/commands/manifest/migrate_cmd.py +87 -0
- dpone/commands/manifest/render_cmd.py +55 -0
- dpone/commands/manifest/stats_cmd.py +66 -0
- dpone/commands/manifest/validate_cmd.py +60 -0
- dpone/commands/manifest/verify_cmd.py +86 -0
- dpone/commands/registry.py +175 -0
- dpone/config/__init__.py +11 -0
- dpone/config/env.py +143 -0
- dpone/config/load_config.py +166 -0
- dpone/contracts/__init__.py +31 -0
- dpone/contracts/api_sources.py +86 -0
- dpone/contracts/errors.py +11 -0
- dpone/contracts/process_types.py +41 -0
- dpone/contracts/run_context.py +17 -0
- dpone/contracts/technical_columns.py +165 -0
- dpone/core/__init__.py +49 -0
- dpone/core/artifacts.py +3 -0
- dpone/core/dag_builder.py +118 -0
- dpone/core/errors.py +3 -0
- dpone/core/etl_types.py +3 -0
- dpone/core/runtime.py +3 -0
- dpone/core/task_group_builder.py +156 -0
- dpone/credentials/__init__.py +39 -0
- dpone/credentials/config.py +6 -0
- dpone/credentials/factory.py +6 -0
- dpone/credentials/manager.py +6 -0
- dpone/credentials/providers.py +6 -0
- dpone/dag/__init__.py +51 -0
- dpone/dag/config.py +32 -0
- dpone/dag/config_models.py +173 -0
- dpone/dag/config_refs.py +64 -0
- dpone/dag/dag_report.py +637 -0
- dpone/dag/dependency_parser.py +204 -0
- dpone/dag/deps_end_to_end_explain.py +453 -0
- dpone/dag/edge_end_to_end_explain.py +278 -0
- dpone/dag/edge_explain.py +485 -0
- dpone/dag/edge_resolver.py +366 -0
- dpone/dag/finder.py +164 -0
- dpone/dag/graph.py +83 -0
- dpone/dag/graph_algorithms.py +87 -0
- dpone/dag/graph_relationships.py +59 -0
- dpone/dag/load_config_builder.py +353 -0
- dpone/dag/loader.py +95 -0
- dpone/dag/manager.py +135 -0
- dpone/dag/manifest_chain_loader.py +101 -0
- dpone/dag/node_end_to_end_explain.py +151 -0
- dpone/dag/node_explain.py +140 -0
- dpone/dag/node_factory.py +25 -0
- dpone/dag/node_registry.py +79 -0
- dpone/dag/parse_trace.py +98 -0
- dpone/dag/post_parse_explain.py +337 -0
- dpone/dag/process.py +42 -0
- dpone/dag/process_config_parser.py +138 -0
- dpone/dag/resolver.py +68 -0
- dpone/dag/subgraph.py +161 -0
- dpone/dag/task_group_index.py +49 -0
- dpone/dag/yaml_types.py +63 -0
- dpone/etl/__init__.py +39 -0
- dpone/etl/processor.py +6 -0
- dpone/etl_logging/__init__.py +39 -0
- dpone/etl_logging/etl_logger.py +6 -0
- dpone/lib/connectors/__init__.py +39 -0
- dpone/lib/connectors/api/__init__.py +6 -0
- dpone/lib/connectors/api/base.py +6 -0
- dpone/lib/connectors/api/cbr.py +6 -0
- dpone/lib/connectors/api/google_ads.py +6 -0
- dpone/lib/connectors/api/google_sheets.py +6 -0
- dpone/lib/connectors/api/omnidesk.py +6 -0
- dpone/lib/connectors/api/openexchangerates.py +6 -0
- dpone/lib/connectors/api/yandex_webmaster.py +6 -0
- dpone/lib/connectors/base.py +9 -0
- dpone/lib/connectors/bigquery.py +6 -0
- dpone/lib/connectors/clickhouse.py +6 -0
- dpone/lib/connectors/postgres.py +6 -0
- dpone/lib/connectors/proxy.py +6 -0
- dpone/lib/technical_columns.py +3 -0
- dpone/lib/utils/__init__.py +15 -0
- dpone/lib/utils/data_type_mapper.py +3 -0
- dpone/lib/utils/dict_config_handler.py +32 -0
- dpone/lib/utils/gcs/__init__.py +41 -0
- dpone/lib/utils/gcs/cleaner.py +259 -0
- dpone/lib/utils/gcs/client.py +73 -0
- dpone/lib/utils/gcs/hmac_credentials.py +123 -0
- dpone/lib/utils/gcs/path_manager.py +193 -0
- dpone/lib/utils/gcs/uri.py +117 -0
- dpone/lib/utils/safe_sql_logging.py +37 -0
- dpone/lib/utils/security.py +116 -0
- dpone/lib/utils/timezone_converter.py +3 -0
- dpone/lib/utils/utils.py +6 -0
- dpone/manifest/__init__.py +28 -0
- dpone/manifest/batch_compiler.py +566 -0
- dpone/manifest/batch_loader.py +101 -0
- dpone/manifest/conventions.py +251 -0
- dpone/manifest/explain.py +204 -0
- dpone/manifest/explain_io.py +37 -0
- dpone/manifest/explain_merge.py +267 -0
- dpone/manifest/explain_models.py +159 -0
- dpone/manifest/explain_trace.py +350 -0
- dpone/manifest/explain_utils.py +187 -0
- dpone/manifest/explain_why.py +210 -0
- dpone/manifest/loader.py +107 -0
- dpone/manifest/migrate.py +674 -0
- dpone/manifest/models.py +40 -0
- dpone/manifest/registry.py +408 -0
- dpone/manifest/registry_lint.py +192 -0
- dpone/manifest/validation.py +729 -0
- dpone/manifest/verify.py +322 -0
- dpone/metrics/__init__.py +0 -0
- dpone/metrics/import_graph.py +170 -0
- dpone/metrics/import_rules.py +282 -0
- dpone/metrics/layer_metrics.py +172 -0
- dpone/metrics/layer_metrics_gate.py +313 -0
- dpone/metrics/loc.py +81 -0
- dpone/metrics/models.py +71 -0
- dpone/metrics/python_imports.py +120 -0
- dpone/metrics/render.py +320 -0
- dpone/output.py +122 -0
- dpone/output_table.py +63 -0
- dpone/ports/__init__.py +16 -0
- dpone/ports/db_connector.py +169 -0
- dpone/ports/filesystem.py +22 -0
- dpone/ports/process_runner.py +56 -0
- dpone/ports/runtime_hydrator.py +75 -0
- dpone/ports/yaml_codec.py +11 -0
- dpone/reconciliation/__init__.py +39 -0
- dpone/reconciliation/base.py +6 -0
- dpone/reconciliation/manager.py +6 -0
- dpone/reconciliation/soft_delete/__init__.py +6 -0
- dpone/reconciliation/soft_delete/bigquery.py +6 -0
- dpone/reconciliation/soft_delete/clickhouse.py +6 -0
- dpone/reconciliation/soft_delete/postgres.py +6 -0
- dpone/reconciliation/soft_delete/registry.py +6 -0
- dpone/reconciliation/tech_tables.py +6 -0
- dpone/runtime/__init__.py +13 -0
- dpone/runtime/api_registry.py +285 -0
- dpone/runtime/artifacts.py +516 -0
- dpone/runtime/bootstrap.py +304 -0
- dpone/runtime/connectors/__init__.py +53 -0
- dpone/runtime/connectors/api/__init__.py +153 -0
- dpone/runtime/connectors/api/appsflyer.py +583 -0
- dpone/runtime/connectors/api/appsflyer_resources.py +111 -0
- dpone/runtime/connectors/api/base.py +840 -0
- dpone/runtime/connectors/api/cbr.py +128 -0
- dpone/runtime/connectors/api/fasttrack.py +335 -0
- dpone/runtime/connectors/api/fasttrack_resources.py +107 -0
- dpone/runtime/connectors/api/google_ads.py +318 -0
- dpone/runtime/connectors/api/google_ads_resources.py +50 -0
- dpone/runtime/connectors/api/google_sheets.py +665 -0
- dpone/runtime/connectors/api/google_sheets_resources.py +50 -0
- dpone/runtime/connectors/api/mindbox.py +477 -0
- dpone/runtime/connectors/api/mindbox_resources.py +109 -0
- dpone/runtime/connectors/api/omnidesk.py +692 -0
- dpone/runtime/connectors/api/openexchangerates.py +315 -0
- dpone/runtime/connectors/api/openexchangerates_resources.py +63 -0
- dpone/runtime/connectors/api/similarweb.py +337 -0
- dpone/runtime/connectors/api/similarweb_resources.py +76 -0
- dpone/runtime/connectors/api/yandex_webmaster.py +496 -0
- dpone/runtime/connectors/api/yandex_webmaster_resources.py +117 -0
- dpone/runtime/connectors/base.py +12 -0
- dpone/runtime/connectors/bigquery.py +584 -0
- dpone/runtime/connectors/clickhouse.py +275 -0
- dpone/runtime/connectors/clickhouse_gcs_export.py +97 -0
- dpone/runtime/connectors/clickhouse_incremental_export.py +239 -0
- dpone/runtime/connectors/clickhouse_partitioning.py +191 -0
- dpone/runtime/connectors/clickhouse_query_ops.py +177 -0
- dpone/runtime/connectors/postgres.py +324 -0
- dpone/runtime/connectors/proxy.py +169 -0
- dpone/runtime/credentials/__init__.py +40 -0
- dpone/runtime/credentials/config.py +39 -0
- dpone/runtime/credentials/factory.py +263 -0
- dpone/runtime/credentials/manager.py +37 -0
- dpone/runtime/credentials/providers.py +189 -0
- dpone/runtime/dev_install.py +290 -0
- dpone/runtime/etl/__init__.py +5 -0
- dpone/runtime/etl/load_config_runtime.py +195 -0
- dpone/runtime/etl/processor.py +243 -0
- dpone/runtime/etl/reconciliation_service.py +194 -0
- dpone/runtime/etl/run_state_tracker.py +101 -0
- dpone/runtime/etl_logging/__init__.py +8 -0
- dpone/runtime/etl_logging/etl_logger.py +962 -0
- dpone/runtime/reconciliation/__init__.py +37 -0
- dpone/runtime/reconciliation/base.py +140 -0
- dpone/runtime/reconciliation/manager.py +304 -0
- dpone/runtime/reconciliation/soft_delete/__init__.py +13 -0
- dpone/runtime/reconciliation/soft_delete/bigquery.py +84 -0
- dpone/runtime/reconciliation/soft_delete/clickhouse.py +84 -0
- dpone/runtime/reconciliation/soft_delete/postgres.py +96 -0
- dpone/runtime/reconciliation/soft_delete/registry.py +104 -0
- dpone/runtime/reconciliation/tech_tables.py +941 -0
- dpone/runtime/sinks/__init__.py +73 -0
- dpone/runtime/sinks/base.py +46 -0
- dpone/runtime/sinks/bigquery.py +200 -0
- dpone/runtime/sinks/bigquery_staging_file_loader.py +180 -0
- dpone/runtime/sinks/bigquery_staging_gcs_loader.py +131 -0
- dpone/runtime/sinks/bigquery_staging_manager.py +242 -0
- dpone/runtime/sinks/postgres.py +86 -0
- dpone/runtime/sinks/postgres_staging_manager.py +179 -0
- dpone/runtime/sinks/staging.py +36 -0
- dpone/runtime/sinks/strategies/__init__.py +60 -0
- dpone/runtime/sinks/strategies/base.py +18 -0
- dpone/runtime/sinks/strategies/bigquery/bigquery_base.py +199 -0
- dpone/runtime/sinks/strategies/bigquery/bigquery_full_refresh.py +215 -0
- dpone/runtime/sinks/strategies/bigquery/bigquery_increment_append.py +148 -0
- dpone/runtime/sinks/strategies/bigquery/bigquery_increment_merge.py +75 -0
- dpone/runtime/sinks/strategies/bigquery/bigquery_replace_strategy.py +73 -0
- dpone/runtime/sinks/strategies/bigquery/dml_helper.py +222 -0
- dpone/runtime/sinks/strategies/bigquery/exchange_logger.py +114 -0
- dpone/runtime/sinks/strategies/bigquery/partition_validation_service.py +93 -0
- dpone/runtime/sinks/strategies/bigquery/target_table_manager.py +282 -0
- dpone/runtime/sinks/strategies/postgres/file_export_loader.py +423 -0
- dpone/runtime/sinks/strategies/postgres/internal_query_loader.py +165 -0
- dpone/runtime/sinks/strategies/postgres/postgres_base.py +175 -0
- dpone/runtime/sinks/strategies/postgres/postgres_full_refresh.py +276 -0
- dpone/runtime/sinks/strategies/postgres/postgres_increment_append.py +304 -0
- dpone/runtime/sinks/strategies/postgres/postgres_increment_merge.py +51 -0
- dpone/runtime/sinks/strategies/postgres/postgres_replace_strategy.py +29 -0
- dpone/runtime/sinks/strategies/postgres/staging_sql_helper.py +142 -0
- dpone/runtime/sinks/strategies/postgres/target_table_manager.py +162 -0
- dpone/runtime/sources/__init__.py +64 -0
- dpone/runtime/sources/api/__init__.py +49 -0
- dpone/runtime/sources/api/appsflyer.py +41 -0
- dpone/runtime/sources/api/base.py +79 -0
- dpone/runtime/sources/api/cbr.py +34 -0
- dpone/runtime/sources/api/fasttrack.py +57 -0
- dpone/runtime/sources/api/google_ads.py +33 -0
- dpone/runtime/sources/api/google_sheets.py +25 -0
- dpone/runtime/sources/api/mindbox.py +36 -0
- dpone/runtime/sources/api/omnidesk.py +108 -0
- dpone/runtime/sources/api/openexchangerates.py +31 -0
- dpone/runtime/sources/api/similarweb.py +22 -0
- dpone/runtime/sources/api/yandex_webmaster.py +39 -0
- dpone/runtime/sources/base.py +32 -0
- dpone/runtime/sources/clickhouse.py +77 -0
- dpone/runtime/sources/postgres.py +102 -0
- dpone/runtime/sources/strategies/__init__.py +83 -0
- dpone/runtime/sources/strategies/api/__init__.py +65 -0
- dpone/runtime/sources/strategies/api/appsflyer/__init__.py +9 -0
- dpone/runtime/sources/strategies/api/appsflyer/common.py +249 -0
- dpone/runtime/sources/strategies/api/appsflyer/full_extract.py +24 -0
- dpone/runtime/sources/strategies/api/appsflyer/incremental_append_extract.py +25 -0
- dpone/runtime/sources/strategies/api/base.py +237 -0
- dpone/runtime/sources/strategies/api/cbr/__init__.py +7 -0
- dpone/runtime/sources/strategies/api/cbr/full_extract.py +75 -0
- dpone/runtime/sources/strategies/api/cbr/incremental_merge_extract.py +90 -0
- dpone/runtime/sources/strategies/api/fasttrack/__init__.py +11 -0
- dpone/runtime/sources/strategies/api/fasttrack/base.py +181 -0
- dpone/runtime/sources/strategies/api/fasttrack/full_extract.py +23 -0
- dpone/runtime/sources/strategies/api/fasttrack/incremental_merge_extract.py +28 -0
- dpone/runtime/sources/strategies/api/google_ads/__init__.py +9 -0
- dpone/runtime/sources/strategies/api/google_ads/full_extract.py +110 -0
- dpone/runtime/sources/strategies/api/google_ads/incremental_merge_extract.py +129 -0
- dpone/runtime/sources/strategies/api/google_ads/specs.py +130 -0
- dpone/runtime/sources/strategies/api/google_sheets/__init__.py +5 -0
- dpone/runtime/sources/strategies/api/google_sheets/full_extract.py +90 -0
- dpone/runtime/sources/strategies/api/mindbox/__init__.py +11 -0
- dpone/runtime/sources/strategies/api/mindbox/base.py +251 -0
- dpone/runtime/sources/strategies/api/mindbox/full_extract.py +42 -0
- dpone/runtime/sources/strategies/api/mindbox/incremental_merge_extract.py +46 -0
- dpone/runtime/sources/strategies/api/mindbox/replace_extract.py +59 -0
- dpone/runtime/sources/strategies/api/omnidesk/__init__.py +17 -0
- dpone/runtime/sources/strategies/api/omnidesk/dictionary_extract.py +156 -0
- dpone/runtime/sources/strategies/api/omnidesk/full_extract.py +254 -0
- dpone/runtime/sources/strategies/api/omnidesk/incremental_append_extract.py +514 -0
- dpone/runtime/sources/strategies/api/omnidesk/incremental_merge_extract.py +321 -0
- dpone/runtime/sources/strategies/api/openexchangerates/__init__.py +9 -0
- dpone/runtime/sources/strategies/api/openexchangerates/full_extract.py +78 -0
- dpone/runtime/sources/strategies/api/openexchangerates/incremental_merge_extract.py +89 -0
- dpone/runtime/sources/strategies/api/similarweb/__init__.py +13 -0
- dpone/runtime/sources/strategies/api/similarweb/base.py +108 -0
- dpone/runtime/sources/strategies/api/similarweb/incremental_append_extract.py +111 -0
- dpone/runtime/sources/strategies/api/similarweb/transformer.py +89 -0
- dpone/runtime/sources/strategies/api/similarweb/validator.py +78 -0
- dpone/runtime/sources/strategies/api/yandex_webmaster/__init__.py +39 -0
- dpone/runtime/sources/strategies/api/yandex_webmaster/common.py +620 -0
- dpone/runtime/sources/strategies/api/yandex_webmaster/full_extract.py +71 -0
- dpone/runtime/sources/strategies/api/yandex_webmaster/incremental_merge_extract.py +81 -0
- dpone/runtime/sources/strategies/api/yandex_webmaster/replace_extract.py +76 -0
- dpone/runtime/sources/strategies/base.py +83 -0
- dpone/runtime/sources/strategies/clickhouse/__init__.py +15 -0
- dpone/runtime/sources/strategies/clickhouse/clickhouse_base.py +258 -0
- dpone/runtime/sources/strategies/clickhouse/clickhouse_full_extract.py +370 -0
- dpone/runtime/sources/strategies/clickhouse/clickhouse_incremental_extract.py +388 -0
- dpone/runtime/sources/strategies/clickhouse/incremental_state_manager.py +118 -0
- dpone/runtime/sources/strategies/postgres/__init__.py +37 -0
- dpone/runtime/sources/strategies/postgres/postgres_base.py +409 -0
- dpone/runtime/sources/strategies/postgres/postgres_full_extract.py +74 -0
- dpone/runtime/sources/strategies/postgres/postgres_incremental_extract.py +309 -0
- dpone/runtime/sources/strategies/postgres/postgres_xmin_extract.py +287 -0
- dpone/runtime/sql_helpers/__init__.py +15 -0
- dpone/runtime/sql_helpers/exchange_queries.py +144 -0
- dpone/runtime/sql_helpers/reconciliation_queries.py +205 -0
- dpone/runtime/sql_helpers/run_state_queries.py +188 -0
- dpone/runtime/sql_helpers/technical_columns_queries.py +334 -0
- dpone/runtime/sql_helpers/xmin_queries.py +102 -0
- dpone/runtime/state/__init__.py +48 -0
- dpone/runtime/state/factory.py +138 -0
- dpone/runtime/state/models.py +87 -0
- dpone/runtime/state/repository.py +27 -0
- dpone/runtime/state/run_state.py +275 -0
- dpone/runtime/state/xmin_storage.py +187 -0
- dpone/runtime/support/__init__.py +42 -0
- dpone/runtime/support/cbr_xml_parser.py +106 -0
- dpone/runtime/support/data_type_mapper.py +813 -0
- dpone/runtime/support/fasttrack_columns.py +81 -0
- dpone/runtime/support/fasttrack_dates.py +103 -0
- dpone/runtime/support/gcs.py +122 -0
- dpone/runtime/support/technical_columns.py +3 -0
- dpone/runtime/support/timezone.py +249 -0
- dpone/runtime/xmin/__init__.py +5 -0
- dpone/runtime/xmin/manager.py +170 -0
- dpone/schema/etl-batch-manifest.schema.json +1208 -0
- dpone/schema/etl-config.schema.json +859 -0
- dpone/services/__init__.py +0 -0
- dpone/services/ci/__init__.py +43 -0
- dpone/services/ci/argocd_promote.py +277 -0
- dpone/services/ci/gitlab_mr.py +132 -0
- dpone/services/ci/snapshot_version.py +33 -0
- dpone/services/ci/yaml_update.py +102 -0
- dpone/services/dag/__init__.py +19 -0
- dpone/services/dag/load_context.py +136 -0
- dpone/services/dag/path_view.py +83 -0
- dpone/services/dag/views/__init__.py +35 -0
- dpone/services/dag/views/common.py +95 -0
- dpone/services/dag/views/deps.py +45 -0
- dpone/services/dag/views/edge.py +95 -0
- dpone/services/dag/views/edge_e2e.py +82 -0
- dpone/services/dag/views/node.py +35 -0
- dpone/services/dag/views/node_e2e.py +54 -0
- dpone/services/dag/views/report.py +58 -0
- dpone/services/docs/__init__.py +21 -0
- dpone/services/docs/check_compatibility_service.py +81 -0
- dpone/services/docs/check_docs_service.py +82 -0
- dpone/services/docs/check_import_rules_service.py +44 -0
- dpone/services/docs/check_layer_metrics_service.py +149 -0
- dpone/services/docs/check_removal_readiness_service.py +79 -0
- dpone/services/docs/cli_reference.py +161 -0
- dpone/services/docs/compatibility_registry.py +235 -0
- dpone/services/docs/deprecation_roadmap.py +124 -0
- dpone/services/docs/generated_block.py +48 -0
- dpone/services/docs/markdown_links.py +242 -0
- dpone/services/docs/shim_removal_plan.py +234 -0
- dpone/services/docs/update_cli_reference_service.py +44 -0
- dpone/services/docs/update_deprecation_roadmap_service.py +53 -0
- dpone/services/docs/update_dev_metrics_service.py +145 -0
- dpone/services/docs/update_shim_removal_plan_service.py +59 -0
- dpone/services/manifest/__init__.py +19 -0
- dpone/services/manifest/load_context.py +115 -0
- dpone/services/manifest/views/__init__.py +24 -0
- dpone/services/manifest/views/common.py +34 -0
- dpone/services/manifest/views/explain.py +31 -0
- dpone/services/manifest/views/list.py +47 -0
- dpone/services/manifest/views/migrate.py +38 -0
- dpone/services/manifest/views/registry_lint.py +41 -0
- dpone/services/manifest/views/render.py +32 -0
- dpone/services/manifest/views/stats.py +30 -0
- dpone/services/manifest/views/validate.py +41 -0
- dpone/services/manifest/views/verify.py +22 -0
- dpone/sink/__init__.py +39 -0
- dpone/sink/base.py +6 -0
- dpone/sink/bigquery.py +6 -0
- dpone/sink/postgres.py +6 -0
- dpone/sink/staging.py +6 -0
- dpone/sink/strategies/__init__.py +6 -0
- dpone/sink/strategies/base.py +6 -0
- dpone/sink/strategies/bigquery/bigquery_base.py +6 -0
- dpone/sink/strategies/bigquery/bigquery_full_refresh.py +6 -0
- dpone/sink/strategies/bigquery/bigquery_increment_append.py +6 -0
- dpone/sink/strategies/bigquery/bigquery_increment_merge.py +6 -0
- dpone/sink/strategies/bigquery/bigquery_replace_strategy.py +6 -0
- dpone/sink/strategies/postgres/postgres_base.py +6 -0
- dpone/sink/strategies/postgres/postgres_full_refresh.py +6 -0
- dpone/sink/strategies/postgres/postgres_increment_append.py +6 -0
- dpone/sink/strategies/postgres/postgres_increment_merge.py +6 -0
- dpone/sink/strategies/postgres/postgres_replace_strategy.py +6 -0
- dpone/source/__init__.py +39 -0
- dpone/source/api/__init__.py +6 -0
- dpone/source/api/base.py +6 -0
- dpone/source/api/cbr.py +6 -0
- dpone/source/api/google_ads.py +6 -0
- dpone/source/api/google_sheets.py +6 -0
- dpone/source/api/omnidesk.py +6 -0
- dpone/source/api/openexchangerates.py +6 -0
- dpone/source/api/yandex_webmaster.py +6 -0
- dpone/source/base.py +6 -0
- dpone/source/clickhouse.py +6 -0
- dpone/source/postgres.py +6 -0
- dpone/source/strategies/__init__.py +6 -0
- dpone/source/strategies/api/__init__.py +6 -0
- dpone/source/strategies/api/base.py +6 -0
- dpone/source/strategies/api/cbr/__init__.py +6 -0
- dpone/source/strategies/api/cbr/full_extract.py +6 -0
- dpone/source/strategies/api/cbr/incremental_merge_extract.py +6 -0
- dpone/source/strategies/api/google_ads/__init__.py +6 -0
- dpone/source/strategies/api/google_ads/full_extract.py +6 -0
- dpone/source/strategies/api/google_ads/incremental_merge_extract.py +6 -0
- dpone/source/strategies/api/google_ads/specs.py +6 -0
- dpone/source/strategies/api/google_sheets/__init__.py +6 -0
- dpone/source/strategies/api/google_sheets/full_extract.py +6 -0
- dpone/source/strategies/api/omnidesk/__init__.py +6 -0
- dpone/source/strategies/api/omnidesk/dictionary_extract.py +6 -0
- dpone/source/strategies/api/omnidesk/full_extract.py +6 -0
- dpone/source/strategies/api/omnidesk/incremental_append_extract.py +6 -0
- dpone/source/strategies/api/omnidesk/incremental_merge_extract.py +6 -0
- dpone/source/strategies/api/yandex_webmaster/__init__.py +6 -0
- dpone/source/strategies/api/yandex_webmaster/common.py +6 -0
- dpone/source/strategies/api/yandex_webmaster/full_extract.py +6 -0
- dpone/source/strategies/api/yandex_webmaster/incremental_merge_extract.py +6 -0
- dpone/source/strategies/api/yandex_webmaster/replace_extract.py +6 -0
- dpone/source/strategies/base.py +6 -0
- dpone/source/strategies/clickhouse/__init__.py +6 -0
- dpone/source/strategies/clickhouse/clickhouse_base.py +6 -0
- dpone/source/strategies/clickhouse/clickhouse_full_extract.py +6 -0
- dpone/source/strategies/clickhouse/clickhouse_incremental_extract.py +6 -0
- dpone/source/strategies/clickhouse/incremental_state_manager.py +6 -0
- dpone/source/strategies/postgres/__init__.py +6 -0
- dpone/source/strategies/postgres/postgres_base.py +6 -0
- dpone/source/strategies/postgres/postgres_full_extract.py +6 -0
- dpone/source/strategies/postgres/postgres_incremental_extract.py +6 -0
- dpone/source/strategies/postgres/postgres_xmin_extract.py +6 -0
- dpone/sql_helpers/__init__.py +39 -0
- dpone/sql_helpers/exchange_queries.py +6 -0
- dpone/sql_helpers/reconciliation_queries.py +6 -0
- dpone/sql_helpers/run_state_queries.py +6 -0
- dpone/sql_helpers/technical_columns_queries.py +6 -0
- dpone/sql_helpers/xmin_queries.py +6 -0
- dpone/state/__init__.py +39 -0
- dpone/state/factory.py +6 -0
- dpone/state/repository.py +6 -0
- dpone/state/run_state.py +6 -0
- dpone/state/xmin_storage.py +6 -0
- dpone/xmin/__init__.py +39 -0
- dpone/xmin/manager.py +6 -0
- dpone/yaml_config_handler/__init__.py +42 -0
- dpone/yaml_config_handler/config.py +9 -0
- dpone/yaml_config_handler/dag_report.py +9 -0
- dpone/yaml_config_handler/deps_end_to_end_explain.py +9 -0
- dpone/yaml_config_handler/edge_end_to_end_explain.py +9 -0
- dpone/yaml_config_handler/edge_explain.py +9 -0
- dpone/yaml_config_handler/finder.py +9 -0
- dpone/yaml_config_handler/graph.py +9 -0
- dpone/yaml_config_handler/loader.py +9 -0
- dpone/yaml_config_handler/manager.py +9 -0
- dpone/yaml_config_handler/node_end_to_end_explain.py +9 -0
- dpone/yaml_config_handler/node_explain.py +9 -0
- dpone/yaml_config_handler/parse_trace.py +9 -0
- dpone/yaml_config_handler/post_parse_explain.py +9 -0
- dpone/yaml_config_handler/process.py +9 -0
- dpone/yaml_config_handler/resolver.py +9 -0
- dpone/yaml_config_handler/subgraph.py +9 -0
- dpone/yaml_config_handler/yaml_types.py +9 -0
- dpone-0.1.0.dist-info/METADATA +290 -0
- dpone-0.1.0.dist-info/RECORD +516 -0
- dpone-0.1.0.dist-info/WHEEL +4 -0
- dpone-0.1.0.dist-info/entry_points.txt +5 -0
dpone/.env.example
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Конфигурация окружения для dpone ETL Framework
|
|
2
|
+
#
|
|
3
|
+
# ENV_CODE определяет окружение (dev/prod/etc.)
|
|
4
|
+
# и используется как vault_mount_point для всех подключений
|
|
5
|
+
#
|
|
6
|
+
# Скопируйте этот файл в .env и настройте под своё окружение:
|
|
7
|
+
# cp .env.example .env
|
|
8
|
+
|
|
9
|
+
# =============================================================================
|
|
10
|
+
# ОБЯЗАТЕЛЬНЫЕ ПЕРЕМЕННЫЕ
|
|
11
|
+
# =============================================================================
|
|
12
|
+
|
|
13
|
+
# Абсолютный путь к корню dpone
|
|
14
|
+
# Используется для определения MANIFEST_DIR (etl-process-manifest/)
|
|
15
|
+
# Пример: /opt/airflow/plugins/dpone
|
|
16
|
+
DPONE_PROJECT_DIR=/opt/airflow/plugins/dpone
|
|
17
|
+
|
|
18
|
+
# Код окружения (vault mount point)
|
|
19
|
+
# Возможные значения: dev, prod, stage
|
|
20
|
+
# По умолчанию: dev (если не указано)
|
|
21
|
+
ENV_CODE=dev
|
|
22
|
+
|
|
23
|
+
# =============================================================================
|
|
24
|
+
# ОПЦИОНАЛЬНЫЕ ПЕРЕМЕННЫЕ
|
|
25
|
+
# =============================================================================
|
|
26
|
+
|
|
27
|
+
# Директория для временных файлов экспорта
|
|
28
|
+
# По умолчанию: системная временная директория
|
|
29
|
+
# DPONE_EXPORT_TMP_DIR=/tmp
|
|
30
|
+
|
|
31
|
+
# Уровень сжатия для gzip экспорта (1-9)
|
|
32
|
+
# По умолчанию: 1 (быстрое сжатие)
|
|
33
|
+
# DPONE_EXPORT_GZIP_LEVEL=1
|
|
34
|
+
|
|
35
|
+
# Размер буфера для экспорта (в байтах)
|
|
36
|
+
# По умолчанию: 16 MB
|
|
37
|
+
# DPONE_EXPORT_BUFFER_SIZE=16777216
|
|
38
|
+
|
|
39
|
+
# Порог размера файла для загрузки через GCS (в MB)
|
|
40
|
+
# Файлы больше этого размера загружаются через GCS, меньше - напрямую
|
|
41
|
+
# По умолчанию: 1000 MB
|
|
42
|
+
# DPONE_GCS_FILE_SIZE_THRESHOLD_MB=1000
|
|
43
|
+
|
|
44
|
+
# Максимальное количество плохих записей для BigQuery Load Job
|
|
45
|
+
# По умолчанию: 100
|
|
46
|
+
# DPONE_BQ_MAX_BAD_RECORDS=100
|
|
47
|
+
|
|
48
|
+
# Размер batch для snapshot операций
|
|
49
|
+
# По умолчанию: 50000
|
|
50
|
+
# DPONE_SNAPSHOT_BATCH_SIZE=50000
|
|
51
|
+
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# ИСПОЛЬЗОВАНИЕ ENV_CODE
|
|
54
|
+
# =============================================================================
|
|
55
|
+
#
|
|
56
|
+
# ENV_CODE автоматически используется как vault_mount_point для:
|
|
57
|
+
# 1. source.vault_mount_point (если не переопределён в YAML)
|
|
58
|
+
# 2. sink.vault_mount_point (если не переопределён в YAML)
|
|
59
|
+
# 3. proxy.vault_mount_point (если не переопределён в YAML)
|
|
60
|
+
# 4. state.vault_mount_point (всегда, блок state можно не указывать в YAML)
|
|
61
|
+
#
|
|
62
|
+
# Для dev/prod vault_path автоматически подставляется:
|
|
63
|
+
# - dev: gcp/tripster-dp-dev/bq/dp-gbq-etl-svc
|
|
64
|
+
# - prod: gcp/tripster-dp-prod/bq/dp-gbq-etl-svc
|
|
65
|
+
#
|
|
66
|
+
# Для других окружений (staging, test) требуется явно указать:
|
|
67
|
+
# state:
|
|
68
|
+
# vault_path: gcp/tripster-dp-staging/bq/dp-gbq-etl-svc
|
|
69
|
+
#
|
|
70
|
+
# =============================================================================
|
dpone/.gitignore
ADDED
|
File without changes
|
dpone/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""dpone package exports.
|
|
2
|
+
|
|
3
|
+
Keep imports lazy to avoid importing heavy/optional dependencies on `import dpone`.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from importlib import import_module
|
|
9
|
+
from importlib.metadata import PackageNotFoundError
|
|
10
|
+
from importlib.metadata import version as _distribution_version
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"__version__",
|
|
15
|
+
"PostgresConnector",
|
|
16
|
+
"ETLProcess",
|
|
17
|
+
"ETLProcessConfig",
|
|
18
|
+
"RunContext",
|
|
19
|
+
"PostgresSink",
|
|
20
|
+
"PostgresSource",
|
|
21
|
+
"LoadConfig",
|
|
22
|
+
"LoadStrategy",
|
|
23
|
+
"ReconciliationManager",
|
|
24
|
+
"XMinStateQueries",
|
|
25
|
+
"RunStateQueries",
|
|
26
|
+
"ReconciliationQueries",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
__version__ = _distribution_version("dpone")
|
|
31
|
+
except PackageNotFoundError: # pragma: no cover - editable installs provide metadata in tests
|
|
32
|
+
__version__ = "0.0.0"
|
|
33
|
+
|
|
34
|
+
# name -> "module:attribute"
|
|
35
|
+
_EXPORTS: dict[str, str] = {
|
|
36
|
+
"PostgresConnector": "dpone.lib.connectors:PostgresConnector",
|
|
37
|
+
"ETLProcess": "dpone.core:ETLProcess",
|
|
38
|
+
"ETLProcessConfig": "dpone.core:ETLProcessConfig",
|
|
39
|
+
"RunContext": "dpone.core:RunContext",
|
|
40
|
+
"PostgresSink": "dpone.sink:PostgresSink",
|
|
41
|
+
"PostgresSource": "dpone.source:PostgresSource",
|
|
42
|
+
"LoadConfig": "dpone.config:LoadConfig",
|
|
43
|
+
"LoadStrategy": "dpone.config:LoadStrategy",
|
|
44
|
+
"ReconciliationManager": "dpone.reconciliation:ReconciliationManager",
|
|
45
|
+
"XMinStateQueries": "dpone.sql_helpers:XMinStateQueries",
|
|
46
|
+
"RunStateQueries": "dpone.sql_helpers:RunStateQueries",
|
|
47
|
+
"ReconciliationQueries": "dpone.sql_helpers:ReconciliationQueries",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def __getattr__(name: str) -> Any:
|
|
52
|
+
target = _EXPORTS.get(name)
|
|
53
|
+
if not target:
|
|
54
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
55
|
+
|
|
56
|
+
module_name, attr = target.split(":")
|
|
57
|
+
mod = import_module(module_name)
|
|
58
|
+
|
|
59
|
+
value = getattr(mod, attr)
|
|
60
|
+
globals()[name] = value # кешируем, чтобы второй раз не импортировать
|
|
61
|
+
return value
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..ports.filesystem import FileSystem
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LocalFileSystem(FileSystem):
|
|
10
|
+
def read_text(self, path: Path, *, encoding: str = "utf-8") -> str:
|
|
11
|
+
return path.read_text(encoding=encoding)
|
|
12
|
+
|
|
13
|
+
def write_text(self, path: Path, data: str, *, encoding: str = "utf-8") -> None:
|
|
14
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
path.write_text(data, encoding=encoding)
|
|
16
|
+
|
|
17
|
+
def exists(self, path: Path) -> bool:
|
|
18
|
+
return path.exists()
|
|
19
|
+
|
|
20
|
+
def glob(self, root: Path, pattern: str) -> Iterable[Path]:
|
|
21
|
+
return root.glob(pattern)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..ports.filesystem import FileSystem
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class InMemoryFileSystem(FileSystem):
|
|
12
|
+
"""Tiny in-memory FS for unit tests."""
|
|
13
|
+
|
|
14
|
+
files: dict[str, str] = field(default_factory=dict)
|
|
15
|
+
|
|
16
|
+
def read_text(self, path: Path, *, encoding: str = "utf-8") -> str:
|
|
17
|
+
key = str(path)
|
|
18
|
+
if key not in self.files:
|
|
19
|
+
raise FileNotFoundError(key)
|
|
20
|
+
return self.files[key]
|
|
21
|
+
|
|
22
|
+
def write_text(self, path: Path, data: str, *, encoding: str = "utf-8") -> None:
|
|
23
|
+
self.files[str(path)] = data
|
|
24
|
+
|
|
25
|
+
def exists(self, path: Path) -> bool:
|
|
26
|
+
return str(path) in self.files
|
|
27
|
+
|
|
28
|
+
def glob(self, root: Path, pattern: str) -> Iterable[Path]:
|
|
29
|
+
# Minimal glob: not needed for now.
|
|
30
|
+
raise NotImplementedError("InMemoryFileSystem.glob is not implemented")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from ..ports.yaml_codec import YamlCodec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PyYamlCodec(YamlCodec):
|
|
11
|
+
def load(self, text: str) -> Any:
|
|
12
|
+
return yaml.safe_load(text)
|
|
13
|
+
|
|
14
|
+
def dump(self, obj: Any) -> str:
|
|
15
|
+
# diff-friendly defaults
|
|
16
|
+
return yaml.safe_dump(obj, sort_keys=False, allow_unicode=True)
|
dpone/app/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def build_root_parser():
|
|
5
|
+
"""Return the canonical argparse tree for CLI reference generation.
|
|
6
|
+
|
|
7
|
+
Kept in the app layer so docs/services can depend on it without importing
|
|
8
|
+
dpone.cli.* directly.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ..cli.parser import build_parser
|
|
12
|
+
|
|
13
|
+
return build_parser()
|
dpone/app/context.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from ..adapters.fs_local import LocalFileSystem
|
|
7
|
+
from ..adapters.yaml_pyyaml import PyYamlCodec
|
|
8
|
+
from ..ports.filesystem import FileSystem
|
|
9
|
+
from ..ports.yaml_codec import YamlCodec
|
|
10
|
+
from .settings import Settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class AppContext:
|
|
15
|
+
"""Composition root (DI container) for dpone CLI.
|
|
16
|
+
|
|
17
|
+
In dpone we intentionally avoid a heavy DI framework.
|
|
18
|
+
AppContext is the single place where we build and cache dependencies.
|
|
19
|
+
|
|
20
|
+
In next refactoring steps we will attach services here.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
settings: Settings
|
|
24
|
+
logger: logging.Logger
|
|
25
|
+
fs: FileSystem
|
|
26
|
+
yaml: YamlCodec
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_env(cls, *, logger: logging.Logger) -> AppContext:
|
|
30
|
+
settings = Settings.from_env()
|
|
31
|
+
fs = LocalFileSystem()
|
|
32
|
+
yaml = PyYamlCodec()
|
|
33
|
+
return cls(settings=settings, logger=logger, fs=fs, yaml=yaml)
|
dpone/app/logging.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_logging() -> logging.Logger:
|
|
8
|
+
"""Configure dpone logger.
|
|
9
|
+
|
|
10
|
+
KISS:
|
|
11
|
+
- no external logging framework
|
|
12
|
+
- env-controlled level
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
level_raw = os.getenv("DPONE_LOG_LEVEL", "INFO").upper().strip()
|
|
16
|
+
level = getattr(logging, level_raw, logging.INFO)
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(
|
|
19
|
+
level=level,
|
|
20
|
+
format="%(levelname)s: %(message)s",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return logging.getLogger("dpone")
|
dpone/app/settings.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Settings:
|
|
10
|
+
"""Typed settings for dpone.
|
|
11
|
+
|
|
12
|
+
Goal:
|
|
13
|
+
- keep defaults predictable
|
|
14
|
+
- avoid module-level side effects
|
|
15
|
+
- keep CLI runnable in local dev
|
|
16
|
+
|
|
17
|
+
NOTE:
|
|
18
|
+
Runtime/Airflow may still rely on DPONE_PROJECT_DIR, but Settings should
|
|
19
|
+
degrade gracefully for local tooling (docs/metrics).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
repo_root: Path
|
|
23
|
+
project_dir: Path
|
|
24
|
+
manifest_dir: Path
|
|
25
|
+
sources_registry_paths: tuple[Path, ...]
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def _detect_repo_root(start: Path) -> Path:
|
|
29
|
+
"""Best-effort repo root detection.
|
|
30
|
+
|
|
31
|
+
We search upwards for `pyproject.toml`.
|
|
32
|
+
If not found, fallback to `start`.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
cur = start.resolve()
|
|
36
|
+
for _ in range(10):
|
|
37
|
+
if (cur / "pyproject.toml").exists():
|
|
38
|
+
return cur
|
|
39
|
+
if cur.parent == cur:
|
|
40
|
+
break
|
|
41
|
+
cur = cur.parent
|
|
42
|
+
return start.resolve()
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_env(cls, *, cwd: Path | None = None) -> Settings:
|
|
46
|
+
cwd = (cwd or Path.cwd()).resolve()
|
|
47
|
+
repo_root = cls._detect_repo_root(cwd)
|
|
48
|
+
|
|
49
|
+
project_dir_raw = os.getenv("DPONE_PROJECT_DIR", "").strip()
|
|
50
|
+
project_dir = Path(project_dir_raw).resolve() if project_dir_raw else repo_root
|
|
51
|
+
|
|
52
|
+
manifest_dir = project_dir / "etl-process-manifest"
|
|
53
|
+
|
|
54
|
+
raw_registry = os.getenv("DPONE_SOURCES_REGISTRY", "").strip()
|
|
55
|
+
paths: list[Path] = []
|
|
56
|
+
if raw_registry:
|
|
57
|
+
for part in [p.strip() for p in raw_registry.split(",") if p.strip()]:
|
|
58
|
+
p = Path(part)
|
|
59
|
+
if not p.is_absolute():
|
|
60
|
+
p = project_dir / p
|
|
61
|
+
paths.append(p)
|
|
62
|
+
|
|
63
|
+
return cls(
|
|
64
|
+
repo_root=repo_root,
|
|
65
|
+
project_dir=project_dir,
|
|
66
|
+
manifest_dir=manifest_dir,
|
|
67
|
+
sources_registry_paths=tuple(paths),
|
|
68
|
+
)
|
dpone/cli/__init__.py
ADDED
dpone/cli/legacy.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Deprecated compatibility shim for the old CLI module.
|
|
2
|
+
|
|
3
|
+
`dpone.cli.legacy` used to contain a very large monolithic argparse-based CLI.
|
|
4
|
+
All command implementations now live in:
|
|
5
|
+
|
|
6
|
+
- ``dpone.cli.main`` / ``dpone.cli.parser``
|
|
7
|
+
- ``dpone.commands.*``
|
|
8
|
+
- ``dpone.services.*``
|
|
9
|
+
- ``dpone.cli_render.*``
|
|
10
|
+
|
|
11
|
+
This module stays only to preserve compatibility for code that still imports
|
|
12
|
+
``dpone.cli.legacy.main`` or executes ``python -m dpone.cli.legacy``.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
import warnings
|
|
20
|
+
from typing import Final
|
|
21
|
+
|
|
22
|
+
from .main import main as _main
|
|
23
|
+
from .parser import build_parser as _build_parser
|
|
24
|
+
|
|
25
|
+
_DEPRECATION_MESSAGE: Final[str] = (
|
|
26
|
+
"`dpone.cli.legacy` is deprecated; use `dpone.cli.main` / `dpone` instead. "
|
|
27
|
+
"The old monolithic CLI module has been replaced with commands/services/renderers."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_REMOVED_EXPORTS: Final[dict[str, str]] = {
|
|
32
|
+
"run_legacy": "Use `dpone` / `dpone.cli.main.main(...)` or specific command modules.",
|
|
33
|
+
"_cmd_manifest_list": "Manifest commands now live in `dpone.commands.manifest.*`.",
|
|
34
|
+
"_cmd_manifest_render": "Manifest commands now live in `dpone.commands.manifest.*`.",
|
|
35
|
+
"_cmd_manifest_explain": "Manifest commands now live in `dpone.commands.manifest.*`.",
|
|
36
|
+
"_cmd_manifest_validate": "Manifest commands now live in `dpone.commands.manifest.*`.",
|
|
37
|
+
"_cmd_manifest_migrate": "Manifest commands now live in `dpone.commands.manifest.*`.",
|
|
38
|
+
"_cmd_manifest_verify": "Manifest commands now live in `dpone.commands.manifest.*`.",
|
|
39
|
+
"_cmd_dag_explain_edge": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
40
|
+
"_cmd_dag_explain_edge_e2e": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
41
|
+
"_cmd_dag_explain_node": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
42
|
+
"_cmd_dag_explain_node_e2e": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
43
|
+
"_cmd_dag_list_edges": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
44
|
+
"_cmd_dag_subgraph": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
45
|
+
"_cmd_dag_report": "DAG commands now live in `dpone.commands.dag.*`.",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _warn() -> None:
|
|
50
|
+
warnings.warn(_DEPRECATION_MESSAGE, DeprecationWarning, stacklevel=2)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
54
|
+
"""Return the canonical parser, but emit a deprecation warning."""
|
|
55
|
+
|
|
56
|
+
_warn()
|
|
57
|
+
return _build_parser()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main(argv: list[str] | None = None) -> None:
|
|
61
|
+
"""Run the canonical CLI through the deprecated legacy entrypoint."""
|
|
62
|
+
|
|
63
|
+
_warn()
|
|
64
|
+
_main(argv)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def __getattr__(name: str):
|
|
68
|
+
if name in _REMOVED_EXPORTS:
|
|
69
|
+
raise AttributeError(f"`dpone.cli.legacy.{name}` was removed. {_REMOVED_EXPORTS[name]}")
|
|
70
|
+
raise AttributeError(name)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main(sys.argv[1:])
|
dpone/cli/main.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from ..app.context import AppContext
|
|
6
|
+
from ..app.logging import setup_logging
|
|
7
|
+
from ..contracts.errors import ETLConfigurationError
|
|
8
|
+
from .parser import build_parser
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(argv: list[str] | None = None) -> None:
|
|
12
|
+
"""Run the canonical dpone CLI.
|
|
13
|
+
|
|
14
|
+
Parsing happens before AppContext construction so `--help` and parser errors
|
|
15
|
+
remain lightweight and do not touch filesystem/env-heavy setup.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
parser = build_parser()
|
|
19
|
+
args = parser.parse_args(argv)
|
|
20
|
+
|
|
21
|
+
cmd = getattr(args, "_command", None)
|
|
22
|
+
if cmd is None:
|
|
23
|
+
parser.print_help()
|
|
24
|
+
raise SystemExit(2)
|
|
25
|
+
|
|
26
|
+
logger = setup_logging()
|
|
27
|
+
ctx = AppContext.from_env(logger=logger)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
code = int(cmd.run(args, ctx))
|
|
31
|
+
except ETLConfigurationError as e:
|
|
32
|
+
logger.error(str(e))
|
|
33
|
+
raise SystemExit(2)
|
|
34
|
+
except KeyboardInterrupt:
|
|
35
|
+
logger.error("Interrupted")
|
|
36
|
+
raise SystemExit(130)
|
|
37
|
+
|
|
38
|
+
raise SystemExit(code)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main(sys.argv[1:])
|
dpone/cli/parser.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ..commands.registry import get_commands
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
9
|
+
parser = argparse.ArgumentParser(prog="dpone")
|
|
10
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
11
|
+
|
|
12
|
+
for cmd in get_commands():
|
|
13
|
+
cmd.register(sub)
|
|
14
|
+
|
|
15
|
+
return parser
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""CLI renderers.
|
|
2
|
+
|
|
3
|
+
Renderers are responsible for converting internal objects into human-friendly
|
|
4
|
+
text output (tables, headings, YAML evidence blocks).
|
|
5
|
+
|
|
6
|
+
Design goals:
|
|
7
|
+
- keep command modules small (parse args -> compute -> render)
|
|
8
|
+
- keep rendering logic reusable and testable
|
|
9
|
+
- do not import runtime-heavy optional dependencies
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from dpone.output import dumps_yaml
|
|
11
|
+
from dpone.output_table import render_table
|
|
12
|
+
|
|
13
|
+
HR = "-" * 80
|
|
14
|
+
HR2 = "=" * 80
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def compact_yaml(value: Any) -> str:
|
|
18
|
+
"""Make values diff-friendly in a single line.
|
|
19
|
+
|
|
20
|
+
Used by "end-to-end" explain commands where we want to show the exact
|
|
21
|
+
depends_on item but keep output compact.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if value is None:
|
|
25
|
+
return "null"
|
|
26
|
+
if isinstance(value, str | int | float | bool):
|
|
27
|
+
return json.dumps(value, ensure_ascii=False)
|
|
28
|
+
|
|
29
|
+
s = yaml.safe_dump(value, sort_keys=True, allow_unicode=True).strip()
|
|
30
|
+
# Collapse to one line
|
|
31
|
+
return " ".join(line.strip() for line in s.splitlines())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _node_line(prefix: str, key: str, value: Any) -> str:
|
|
35
|
+
return f"{prefix}{key}: {value}" if value is not None and value != "" else ""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def render_dag_banner(*, root: Path, base_path: Path, task_count: int, extra: Sequence[str] | None = None) -> str:
|
|
39
|
+
lines = [
|
|
40
|
+
f"DAG root: {root}",
|
|
41
|
+
f"Base dir: {base_path}",
|
|
42
|
+
f"Tasks: {task_count}",
|
|
43
|
+
]
|
|
44
|
+
if extra:
|
|
45
|
+
for x in extra:
|
|
46
|
+
if x:
|
|
47
|
+
lines.append(str(x))
|
|
48
|
+
return "\n".join(lines)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def render_node_block(title: str, node: Mapping[str, Any]) -> str:
|
|
52
|
+
"""Render a node dict as printed by explain_* helpers."""
|
|
53
|
+
|
|
54
|
+
lines: list[str] = [f"\n{title}:"]
|
|
55
|
+
lines.append(f" name: {node.get('name')}")
|
|
56
|
+
lines.append(f" ref: {node.get('ref')}")
|
|
57
|
+
|
|
58
|
+
if node.get("task_group"):
|
|
59
|
+
lines.append(f" task_group:{node.get('task_group')}")
|
|
60
|
+
if node.get("source"):
|
|
61
|
+
lines.append(f" source: {node.get('source')}")
|
|
62
|
+
if node.get("sink"):
|
|
63
|
+
lines.append(f" sink: {node.get('sink')}")
|
|
64
|
+
|
|
65
|
+
return "\n".join(lines)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def render_task_block(title: str, task: Mapping[str, Any]) -> str:
|
|
69
|
+
# alias for older naming
|
|
70
|
+
return render_node_block(title, task)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def render_warnings(warnings: Iterable[str]) -> str:
|
|
74
|
+
out = []
|
|
75
|
+
for w in warnings or ():
|
|
76
|
+
out.append(f"WARN: {w}")
|
|
77
|
+
return "\n".join(out)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------- reasons ----------------
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _reason_kind(reason: Any) -> str:
|
|
84
|
+
if isinstance(reason, dict):
|
|
85
|
+
return str(reason.get("kind") or "")
|
|
86
|
+
return str(getattr(reason, "kind", ""))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _reason_description(reason: Any) -> str:
|
|
90
|
+
if isinstance(reason, dict):
|
|
91
|
+
return str(reason.get("description") or "")
|
|
92
|
+
return str(getattr(reason, "description", ""))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _reason_evidence(reason: Any) -> dict:
|
|
96
|
+
if isinstance(reason, dict):
|
|
97
|
+
ev = reason.get("evidence")
|
|
98
|
+
return ev if isinstance(ev, dict) else {}
|
|
99
|
+
ev = getattr(reason, "evidence", None)
|
|
100
|
+
return ev if isinstance(ev, dict) else {}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def render_reasons_section(
|
|
104
|
+
reasons: Sequence[Any],
|
|
105
|
+
*,
|
|
106
|
+
title: str = "Reasons",
|
|
107
|
+
include_evidence: bool = True,
|
|
108
|
+
) -> str:
|
|
109
|
+
if not reasons:
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
lines: list[str] = [f"\n{title}:"]
|
|
113
|
+
rows = [[_reason_kind(r), _reason_description(r)] for r in reasons]
|
|
114
|
+
lines.append(render_table(["kind", "description"], rows))
|
|
115
|
+
|
|
116
|
+
if include_evidence:
|
|
117
|
+
for r in reasons:
|
|
118
|
+
ev = _reason_evidence(r)
|
|
119
|
+
if not ev:
|
|
120
|
+
continue
|
|
121
|
+
kind = _reason_kind(r)
|
|
122
|
+
lines.append(f"\n[{kind}] evidence:")
|
|
123
|
+
lines.append(dumps_yaml(ev, sort_keys=True).rstrip())
|
|
124
|
+
|
|
125
|
+
return "\n".join(lines)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def render_reasons_compact(reasons: Sequence[Any]) -> str:
|
|
129
|
+
if not reasons:
|
|
130
|
+
return "(no attributed reasons)"
|
|
131
|
+
lines = [f"- {_reason_kind(r)}: {_reason_description(r)}" for r in reasons]
|
|
132
|
+
return "\n".join(lines)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def render_table_section(title: str, headers: Sequence[str], rows: Sequence[Sequence[Any]]) -> str:
|
|
136
|
+
lines: list[str] = [f"\n{title}:"]
|
|
137
|
+
lines.append(render_table(list(headers), [list(r) for r in rows]))
|
|
138
|
+
return "\n".join(lines)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def render_yaml_block(title: str, obj: Any) -> str:
|
|
142
|
+
lines: list[str] = [f"\n{title}:"]
|
|
143
|
+
lines.append(dumps_yaml(obj, sort_keys=True).rstrip())
|
|
144
|
+
return "\n".join(lines)
|