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.
Files changed (516) hide show
  1. dpone/.env.example +70 -0
  2. dpone/.gitignore +0 -0
  3. dpone/__init__.py +61 -0
  4. dpone/adapters/__init__.py +0 -0
  5. dpone/adapters/fs_local.py +21 -0
  6. dpone/adapters/fs_memory.py +30 -0
  7. dpone/adapters/yaml_pyyaml.py +16 -0
  8. dpone/app/__init__.py +0 -0
  9. dpone/app/cli_reference_source.py +13 -0
  10. dpone/app/context.py +33 -0
  11. dpone/app/logging.py +23 -0
  12. dpone/app/settings.py +68 -0
  13. dpone/cli/__init__.py +7 -0
  14. dpone/cli/legacy.py +74 -0
  15. dpone/cli/main.py +42 -0
  16. dpone/cli/parser.py +15 -0
  17. dpone/cli_render/__init__.py +12 -0
  18. dpone/cli_render/dag/__init__.py +3 -0
  19. dpone/cli_render/dag/common.py +144 -0
  20. dpone/cli_render/dag/deps.py +70 -0
  21. dpone/cli_render/dag/e2e_attribution.py +160 -0
  22. dpone/cli_render/dag/edge.py +95 -0
  23. dpone/cli_render/dag/edge_e2e.py +133 -0
  24. dpone/cli_render/dag/node.py +104 -0
  25. dpone/cli_render/dag/node_e2e.py +134 -0
  26. dpone/cli_render/dag/report.py +55 -0
  27. dpone/cli_render/manifest/__init__.py +19 -0
  28. dpone/cli_render/manifest/common.py +114 -0
  29. dpone/cli_render/manifest/explain.py +273 -0
  30. dpone/cli_render/manifest/list.py +11 -0
  31. dpone/cli_render/manifest/migrate.py +15 -0
  32. dpone/cli_render/manifest/registry_lint.py +15 -0
  33. dpone/cli_render/manifest/render.py +13 -0
  34. dpone/cli_render/manifest/stats.py +23 -0
  35. dpone/cli_render/manifest/validate.py +13 -0
  36. dpone/cli_render/manifest/verify.py +20 -0
  37. dpone/commands/__init__.py +0 -0
  38. dpone/commands/base.py +29 -0
  39. dpone/commands/dag/__init__.py +0 -0
  40. dpone/commands/dag/explain_deps_cmd.py +120 -0
  41. dpone/commands/dag/explain_edge_cmd.py +123 -0
  42. dpone/commands/dag/explain_edge_e2e_cmd.py +134 -0
  43. dpone/commands/dag/explain_node_cmd.py +93 -0
  44. dpone/commands/dag/explain_node_e2e_cmd.py +114 -0
  45. dpone/commands/dag/list_edges_cmd.py +309 -0
  46. dpone/commands/dag/report_cmd.py +255 -0
  47. dpone/commands/dag/subgraph_cmd.py +213 -0
  48. dpone/commands/docs/__init__.py +60 -0
  49. dpone/commands/docs/check_compatibility_cmd.py +52 -0
  50. dpone/commands/docs/check_docs_cmd.py +44 -0
  51. dpone/commands/docs/check_import_rules_cmd.py +37 -0
  52. dpone/commands/docs/check_layer_metrics_cmd.py +97 -0
  53. dpone/commands/docs/check_removal_readiness_cmd.py +36 -0
  54. dpone/commands/docs/update_cli_reference_cmd.py +26 -0
  55. dpone/commands/docs/update_deprecation_roadmap_cmd.py +35 -0
  56. dpone/commands/docs/update_dev_metrics_cmd.py +45 -0
  57. dpone/commands/docs/update_shim_removal_plan_cmd.py +29 -0
  58. dpone/commands/func_command.py +62 -0
  59. dpone/commands/manifest/__init__.py +0 -0
  60. dpone/commands/manifest/explain_cmd.py +145 -0
  61. dpone/commands/manifest/lint_registry_cmd.py +64 -0
  62. dpone/commands/manifest/list_cmd.py +65 -0
  63. dpone/commands/manifest/migrate_cmd.py +87 -0
  64. dpone/commands/manifest/render_cmd.py +55 -0
  65. dpone/commands/manifest/stats_cmd.py +66 -0
  66. dpone/commands/manifest/validate_cmd.py +60 -0
  67. dpone/commands/manifest/verify_cmd.py +86 -0
  68. dpone/commands/registry.py +175 -0
  69. dpone/config/__init__.py +11 -0
  70. dpone/config/env.py +143 -0
  71. dpone/config/load_config.py +166 -0
  72. dpone/contracts/__init__.py +31 -0
  73. dpone/contracts/api_sources.py +86 -0
  74. dpone/contracts/errors.py +11 -0
  75. dpone/contracts/process_types.py +41 -0
  76. dpone/contracts/run_context.py +17 -0
  77. dpone/contracts/technical_columns.py +165 -0
  78. dpone/core/__init__.py +49 -0
  79. dpone/core/artifacts.py +3 -0
  80. dpone/core/dag_builder.py +118 -0
  81. dpone/core/errors.py +3 -0
  82. dpone/core/etl_types.py +3 -0
  83. dpone/core/runtime.py +3 -0
  84. dpone/core/task_group_builder.py +156 -0
  85. dpone/credentials/__init__.py +39 -0
  86. dpone/credentials/config.py +6 -0
  87. dpone/credentials/factory.py +6 -0
  88. dpone/credentials/manager.py +6 -0
  89. dpone/credentials/providers.py +6 -0
  90. dpone/dag/__init__.py +51 -0
  91. dpone/dag/config.py +32 -0
  92. dpone/dag/config_models.py +173 -0
  93. dpone/dag/config_refs.py +64 -0
  94. dpone/dag/dag_report.py +637 -0
  95. dpone/dag/dependency_parser.py +204 -0
  96. dpone/dag/deps_end_to_end_explain.py +453 -0
  97. dpone/dag/edge_end_to_end_explain.py +278 -0
  98. dpone/dag/edge_explain.py +485 -0
  99. dpone/dag/edge_resolver.py +366 -0
  100. dpone/dag/finder.py +164 -0
  101. dpone/dag/graph.py +83 -0
  102. dpone/dag/graph_algorithms.py +87 -0
  103. dpone/dag/graph_relationships.py +59 -0
  104. dpone/dag/load_config_builder.py +353 -0
  105. dpone/dag/loader.py +95 -0
  106. dpone/dag/manager.py +135 -0
  107. dpone/dag/manifest_chain_loader.py +101 -0
  108. dpone/dag/node_end_to_end_explain.py +151 -0
  109. dpone/dag/node_explain.py +140 -0
  110. dpone/dag/node_factory.py +25 -0
  111. dpone/dag/node_registry.py +79 -0
  112. dpone/dag/parse_trace.py +98 -0
  113. dpone/dag/post_parse_explain.py +337 -0
  114. dpone/dag/process.py +42 -0
  115. dpone/dag/process_config_parser.py +138 -0
  116. dpone/dag/resolver.py +68 -0
  117. dpone/dag/subgraph.py +161 -0
  118. dpone/dag/task_group_index.py +49 -0
  119. dpone/dag/yaml_types.py +63 -0
  120. dpone/etl/__init__.py +39 -0
  121. dpone/etl/processor.py +6 -0
  122. dpone/etl_logging/__init__.py +39 -0
  123. dpone/etl_logging/etl_logger.py +6 -0
  124. dpone/lib/connectors/__init__.py +39 -0
  125. dpone/lib/connectors/api/__init__.py +6 -0
  126. dpone/lib/connectors/api/base.py +6 -0
  127. dpone/lib/connectors/api/cbr.py +6 -0
  128. dpone/lib/connectors/api/google_ads.py +6 -0
  129. dpone/lib/connectors/api/google_sheets.py +6 -0
  130. dpone/lib/connectors/api/omnidesk.py +6 -0
  131. dpone/lib/connectors/api/openexchangerates.py +6 -0
  132. dpone/lib/connectors/api/yandex_webmaster.py +6 -0
  133. dpone/lib/connectors/base.py +9 -0
  134. dpone/lib/connectors/bigquery.py +6 -0
  135. dpone/lib/connectors/clickhouse.py +6 -0
  136. dpone/lib/connectors/postgres.py +6 -0
  137. dpone/lib/connectors/proxy.py +6 -0
  138. dpone/lib/technical_columns.py +3 -0
  139. dpone/lib/utils/__init__.py +15 -0
  140. dpone/lib/utils/data_type_mapper.py +3 -0
  141. dpone/lib/utils/dict_config_handler.py +32 -0
  142. dpone/lib/utils/gcs/__init__.py +41 -0
  143. dpone/lib/utils/gcs/cleaner.py +259 -0
  144. dpone/lib/utils/gcs/client.py +73 -0
  145. dpone/lib/utils/gcs/hmac_credentials.py +123 -0
  146. dpone/lib/utils/gcs/path_manager.py +193 -0
  147. dpone/lib/utils/gcs/uri.py +117 -0
  148. dpone/lib/utils/safe_sql_logging.py +37 -0
  149. dpone/lib/utils/security.py +116 -0
  150. dpone/lib/utils/timezone_converter.py +3 -0
  151. dpone/lib/utils/utils.py +6 -0
  152. dpone/manifest/__init__.py +28 -0
  153. dpone/manifest/batch_compiler.py +566 -0
  154. dpone/manifest/batch_loader.py +101 -0
  155. dpone/manifest/conventions.py +251 -0
  156. dpone/manifest/explain.py +204 -0
  157. dpone/manifest/explain_io.py +37 -0
  158. dpone/manifest/explain_merge.py +267 -0
  159. dpone/manifest/explain_models.py +159 -0
  160. dpone/manifest/explain_trace.py +350 -0
  161. dpone/manifest/explain_utils.py +187 -0
  162. dpone/manifest/explain_why.py +210 -0
  163. dpone/manifest/loader.py +107 -0
  164. dpone/manifest/migrate.py +674 -0
  165. dpone/manifest/models.py +40 -0
  166. dpone/manifest/registry.py +408 -0
  167. dpone/manifest/registry_lint.py +192 -0
  168. dpone/manifest/validation.py +729 -0
  169. dpone/manifest/verify.py +322 -0
  170. dpone/metrics/__init__.py +0 -0
  171. dpone/metrics/import_graph.py +170 -0
  172. dpone/metrics/import_rules.py +282 -0
  173. dpone/metrics/layer_metrics.py +172 -0
  174. dpone/metrics/layer_metrics_gate.py +313 -0
  175. dpone/metrics/loc.py +81 -0
  176. dpone/metrics/models.py +71 -0
  177. dpone/metrics/python_imports.py +120 -0
  178. dpone/metrics/render.py +320 -0
  179. dpone/output.py +122 -0
  180. dpone/output_table.py +63 -0
  181. dpone/ports/__init__.py +16 -0
  182. dpone/ports/db_connector.py +169 -0
  183. dpone/ports/filesystem.py +22 -0
  184. dpone/ports/process_runner.py +56 -0
  185. dpone/ports/runtime_hydrator.py +75 -0
  186. dpone/ports/yaml_codec.py +11 -0
  187. dpone/reconciliation/__init__.py +39 -0
  188. dpone/reconciliation/base.py +6 -0
  189. dpone/reconciliation/manager.py +6 -0
  190. dpone/reconciliation/soft_delete/__init__.py +6 -0
  191. dpone/reconciliation/soft_delete/bigquery.py +6 -0
  192. dpone/reconciliation/soft_delete/clickhouse.py +6 -0
  193. dpone/reconciliation/soft_delete/postgres.py +6 -0
  194. dpone/reconciliation/soft_delete/registry.py +6 -0
  195. dpone/reconciliation/tech_tables.py +6 -0
  196. dpone/runtime/__init__.py +13 -0
  197. dpone/runtime/api_registry.py +285 -0
  198. dpone/runtime/artifacts.py +516 -0
  199. dpone/runtime/bootstrap.py +304 -0
  200. dpone/runtime/connectors/__init__.py +53 -0
  201. dpone/runtime/connectors/api/__init__.py +153 -0
  202. dpone/runtime/connectors/api/appsflyer.py +583 -0
  203. dpone/runtime/connectors/api/appsflyer_resources.py +111 -0
  204. dpone/runtime/connectors/api/base.py +840 -0
  205. dpone/runtime/connectors/api/cbr.py +128 -0
  206. dpone/runtime/connectors/api/fasttrack.py +335 -0
  207. dpone/runtime/connectors/api/fasttrack_resources.py +107 -0
  208. dpone/runtime/connectors/api/google_ads.py +318 -0
  209. dpone/runtime/connectors/api/google_ads_resources.py +50 -0
  210. dpone/runtime/connectors/api/google_sheets.py +665 -0
  211. dpone/runtime/connectors/api/google_sheets_resources.py +50 -0
  212. dpone/runtime/connectors/api/mindbox.py +477 -0
  213. dpone/runtime/connectors/api/mindbox_resources.py +109 -0
  214. dpone/runtime/connectors/api/omnidesk.py +692 -0
  215. dpone/runtime/connectors/api/openexchangerates.py +315 -0
  216. dpone/runtime/connectors/api/openexchangerates_resources.py +63 -0
  217. dpone/runtime/connectors/api/similarweb.py +337 -0
  218. dpone/runtime/connectors/api/similarweb_resources.py +76 -0
  219. dpone/runtime/connectors/api/yandex_webmaster.py +496 -0
  220. dpone/runtime/connectors/api/yandex_webmaster_resources.py +117 -0
  221. dpone/runtime/connectors/base.py +12 -0
  222. dpone/runtime/connectors/bigquery.py +584 -0
  223. dpone/runtime/connectors/clickhouse.py +275 -0
  224. dpone/runtime/connectors/clickhouse_gcs_export.py +97 -0
  225. dpone/runtime/connectors/clickhouse_incremental_export.py +239 -0
  226. dpone/runtime/connectors/clickhouse_partitioning.py +191 -0
  227. dpone/runtime/connectors/clickhouse_query_ops.py +177 -0
  228. dpone/runtime/connectors/postgres.py +324 -0
  229. dpone/runtime/connectors/proxy.py +169 -0
  230. dpone/runtime/credentials/__init__.py +40 -0
  231. dpone/runtime/credentials/config.py +39 -0
  232. dpone/runtime/credentials/factory.py +263 -0
  233. dpone/runtime/credentials/manager.py +37 -0
  234. dpone/runtime/credentials/providers.py +189 -0
  235. dpone/runtime/dev_install.py +290 -0
  236. dpone/runtime/etl/__init__.py +5 -0
  237. dpone/runtime/etl/load_config_runtime.py +195 -0
  238. dpone/runtime/etl/processor.py +243 -0
  239. dpone/runtime/etl/reconciliation_service.py +194 -0
  240. dpone/runtime/etl/run_state_tracker.py +101 -0
  241. dpone/runtime/etl_logging/__init__.py +8 -0
  242. dpone/runtime/etl_logging/etl_logger.py +962 -0
  243. dpone/runtime/reconciliation/__init__.py +37 -0
  244. dpone/runtime/reconciliation/base.py +140 -0
  245. dpone/runtime/reconciliation/manager.py +304 -0
  246. dpone/runtime/reconciliation/soft_delete/__init__.py +13 -0
  247. dpone/runtime/reconciliation/soft_delete/bigquery.py +84 -0
  248. dpone/runtime/reconciliation/soft_delete/clickhouse.py +84 -0
  249. dpone/runtime/reconciliation/soft_delete/postgres.py +96 -0
  250. dpone/runtime/reconciliation/soft_delete/registry.py +104 -0
  251. dpone/runtime/reconciliation/tech_tables.py +941 -0
  252. dpone/runtime/sinks/__init__.py +73 -0
  253. dpone/runtime/sinks/base.py +46 -0
  254. dpone/runtime/sinks/bigquery.py +200 -0
  255. dpone/runtime/sinks/bigquery_staging_file_loader.py +180 -0
  256. dpone/runtime/sinks/bigquery_staging_gcs_loader.py +131 -0
  257. dpone/runtime/sinks/bigquery_staging_manager.py +242 -0
  258. dpone/runtime/sinks/postgres.py +86 -0
  259. dpone/runtime/sinks/postgres_staging_manager.py +179 -0
  260. dpone/runtime/sinks/staging.py +36 -0
  261. dpone/runtime/sinks/strategies/__init__.py +60 -0
  262. dpone/runtime/sinks/strategies/base.py +18 -0
  263. dpone/runtime/sinks/strategies/bigquery/bigquery_base.py +199 -0
  264. dpone/runtime/sinks/strategies/bigquery/bigquery_full_refresh.py +215 -0
  265. dpone/runtime/sinks/strategies/bigquery/bigquery_increment_append.py +148 -0
  266. dpone/runtime/sinks/strategies/bigquery/bigquery_increment_merge.py +75 -0
  267. dpone/runtime/sinks/strategies/bigquery/bigquery_replace_strategy.py +73 -0
  268. dpone/runtime/sinks/strategies/bigquery/dml_helper.py +222 -0
  269. dpone/runtime/sinks/strategies/bigquery/exchange_logger.py +114 -0
  270. dpone/runtime/sinks/strategies/bigquery/partition_validation_service.py +93 -0
  271. dpone/runtime/sinks/strategies/bigquery/target_table_manager.py +282 -0
  272. dpone/runtime/sinks/strategies/postgres/file_export_loader.py +423 -0
  273. dpone/runtime/sinks/strategies/postgres/internal_query_loader.py +165 -0
  274. dpone/runtime/sinks/strategies/postgres/postgres_base.py +175 -0
  275. dpone/runtime/sinks/strategies/postgres/postgres_full_refresh.py +276 -0
  276. dpone/runtime/sinks/strategies/postgres/postgres_increment_append.py +304 -0
  277. dpone/runtime/sinks/strategies/postgres/postgres_increment_merge.py +51 -0
  278. dpone/runtime/sinks/strategies/postgres/postgres_replace_strategy.py +29 -0
  279. dpone/runtime/sinks/strategies/postgres/staging_sql_helper.py +142 -0
  280. dpone/runtime/sinks/strategies/postgres/target_table_manager.py +162 -0
  281. dpone/runtime/sources/__init__.py +64 -0
  282. dpone/runtime/sources/api/__init__.py +49 -0
  283. dpone/runtime/sources/api/appsflyer.py +41 -0
  284. dpone/runtime/sources/api/base.py +79 -0
  285. dpone/runtime/sources/api/cbr.py +34 -0
  286. dpone/runtime/sources/api/fasttrack.py +57 -0
  287. dpone/runtime/sources/api/google_ads.py +33 -0
  288. dpone/runtime/sources/api/google_sheets.py +25 -0
  289. dpone/runtime/sources/api/mindbox.py +36 -0
  290. dpone/runtime/sources/api/omnidesk.py +108 -0
  291. dpone/runtime/sources/api/openexchangerates.py +31 -0
  292. dpone/runtime/sources/api/similarweb.py +22 -0
  293. dpone/runtime/sources/api/yandex_webmaster.py +39 -0
  294. dpone/runtime/sources/base.py +32 -0
  295. dpone/runtime/sources/clickhouse.py +77 -0
  296. dpone/runtime/sources/postgres.py +102 -0
  297. dpone/runtime/sources/strategies/__init__.py +83 -0
  298. dpone/runtime/sources/strategies/api/__init__.py +65 -0
  299. dpone/runtime/sources/strategies/api/appsflyer/__init__.py +9 -0
  300. dpone/runtime/sources/strategies/api/appsflyer/common.py +249 -0
  301. dpone/runtime/sources/strategies/api/appsflyer/full_extract.py +24 -0
  302. dpone/runtime/sources/strategies/api/appsflyer/incremental_append_extract.py +25 -0
  303. dpone/runtime/sources/strategies/api/base.py +237 -0
  304. dpone/runtime/sources/strategies/api/cbr/__init__.py +7 -0
  305. dpone/runtime/sources/strategies/api/cbr/full_extract.py +75 -0
  306. dpone/runtime/sources/strategies/api/cbr/incremental_merge_extract.py +90 -0
  307. dpone/runtime/sources/strategies/api/fasttrack/__init__.py +11 -0
  308. dpone/runtime/sources/strategies/api/fasttrack/base.py +181 -0
  309. dpone/runtime/sources/strategies/api/fasttrack/full_extract.py +23 -0
  310. dpone/runtime/sources/strategies/api/fasttrack/incremental_merge_extract.py +28 -0
  311. dpone/runtime/sources/strategies/api/google_ads/__init__.py +9 -0
  312. dpone/runtime/sources/strategies/api/google_ads/full_extract.py +110 -0
  313. dpone/runtime/sources/strategies/api/google_ads/incremental_merge_extract.py +129 -0
  314. dpone/runtime/sources/strategies/api/google_ads/specs.py +130 -0
  315. dpone/runtime/sources/strategies/api/google_sheets/__init__.py +5 -0
  316. dpone/runtime/sources/strategies/api/google_sheets/full_extract.py +90 -0
  317. dpone/runtime/sources/strategies/api/mindbox/__init__.py +11 -0
  318. dpone/runtime/sources/strategies/api/mindbox/base.py +251 -0
  319. dpone/runtime/sources/strategies/api/mindbox/full_extract.py +42 -0
  320. dpone/runtime/sources/strategies/api/mindbox/incremental_merge_extract.py +46 -0
  321. dpone/runtime/sources/strategies/api/mindbox/replace_extract.py +59 -0
  322. dpone/runtime/sources/strategies/api/omnidesk/__init__.py +17 -0
  323. dpone/runtime/sources/strategies/api/omnidesk/dictionary_extract.py +156 -0
  324. dpone/runtime/sources/strategies/api/omnidesk/full_extract.py +254 -0
  325. dpone/runtime/sources/strategies/api/omnidesk/incremental_append_extract.py +514 -0
  326. dpone/runtime/sources/strategies/api/omnidesk/incremental_merge_extract.py +321 -0
  327. dpone/runtime/sources/strategies/api/openexchangerates/__init__.py +9 -0
  328. dpone/runtime/sources/strategies/api/openexchangerates/full_extract.py +78 -0
  329. dpone/runtime/sources/strategies/api/openexchangerates/incremental_merge_extract.py +89 -0
  330. dpone/runtime/sources/strategies/api/similarweb/__init__.py +13 -0
  331. dpone/runtime/sources/strategies/api/similarweb/base.py +108 -0
  332. dpone/runtime/sources/strategies/api/similarweb/incremental_append_extract.py +111 -0
  333. dpone/runtime/sources/strategies/api/similarweb/transformer.py +89 -0
  334. dpone/runtime/sources/strategies/api/similarweb/validator.py +78 -0
  335. dpone/runtime/sources/strategies/api/yandex_webmaster/__init__.py +39 -0
  336. dpone/runtime/sources/strategies/api/yandex_webmaster/common.py +620 -0
  337. dpone/runtime/sources/strategies/api/yandex_webmaster/full_extract.py +71 -0
  338. dpone/runtime/sources/strategies/api/yandex_webmaster/incremental_merge_extract.py +81 -0
  339. dpone/runtime/sources/strategies/api/yandex_webmaster/replace_extract.py +76 -0
  340. dpone/runtime/sources/strategies/base.py +83 -0
  341. dpone/runtime/sources/strategies/clickhouse/__init__.py +15 -0
  342. dpone/runtime/sources/strategies/clickhouse/clickhouse_base.py +258 -0
  343. dpone/runtime/sources/strategies/clickhouse/clickhouse_full_extract.py +370 -0
  344. dpone/runtime/sources/strategies/clickhouse/clickhouse_incremental_extract.py +388 -0
  345. dpone/runtime/sources/strategies/clickhouse/incremental_state_manager.py +118 -0
  346. dpone/runtime/sources/strategies/postgres/__init__.py +37 -0
  347. dpone/runtime/sources/strategies/postgres/postgres_base.py +409 -0
  348. dpone/runtime/sources/strategies/postgres/postgres_full_extract.py +74 -0
  349. dpone/runtime/sources/strategies/postgres/postgres_incremental_extract.py +309 -0
  350. dpone/runtime/sources/strategies/postgres/postgres_xmin_extract.py +287 -0
  351. dpone/runtime/sql_helpers/__init__.py +15 -0
  352. dpone/runtime/sql_helpers/exchange_queries.py +144 -0
  353. dpone/runtime/sql_helpers/reconciliation_queries.py +205 -0
  354. dpone/runtime/sql_helpers/run_state_queries.py +188 -0
  355. dpone/runtime/sql_helpers/technical_columns_queries.py +334 -0
  356. dpone/runtime/sql_helpers/xmin_queries.py +102 -0
  357. dpone/runtime/state/__init__.py +48 -0
  358. dpone/runtime/state/factory.py +138 -0
  359. dpone/runtime/state/models.py +87 -0
  360. dpone/runtime/state/repository.py +27 -0
  361. dpone/runtime/state/run_state.py +275 -0
  362. dpone/runtime/state/xmin_storage.py +187 -0
  363. dpone/runtime/support/__init__.py +42 -0
  364. dpone/runtime/support/cbr_xml_parser.py +106 -0
  365. dpone/runtime/support/data_type_mapper.py +813 -0
  366. dpone/runtime/support/fasttrack_columns.py +81 -0
  367. dpone/runtime/support/fasttrack_dates.py +103 -0
  368. dpone/runtime/support/gcs.py +122 -0
  369. dpone/runtime/support/technical_columns.py +3 -0
  370. dpone/runtime/support/timezone.py +249 -0
  371. dpone/runtime/xmin/__init__.py +5 -0
  372. dpone/runtime/xmin/manager.py +170 -0
  373. dpone/schema/etl-batch-manifest.schema.json +1208 -0
  374. dpone/schema/etl-config.schema.json +859 -0
  375. dpone/services/__init__.py +0 -0
  376. dpone/services/ci/__init__.py +43 -0
  377. dpone/services/ci/argocd_promote.py +277 -0
  378. dpone/services/ci/gitlab_mr.py +132 -0
  379. dpone/services/ci/snapshot_version.py +33 -0
  380. dpone/services/ci/yaml_update.py +102 -0
  381. dpone/services/dag/__init__.py +19 -0
  382. dpone/services/dag/load_context.py +136 -0
  383. dpone/services/dag/path_view.py +83 -0
  384. dpone/services/dag/views/__init__.py +35 -0
  385. dpone/services/dag/views/common.py +95 -0
  386. dpone/services/dag/views/deps.py +45 -0
  387. dpone/services/dag/views/edge.py +95 -0
  388. dpone/services/dag/views/edge_e2e.py +82 -0
  389. dpone/services/dag/views/node.py +35 -0
  390. dpone/services/dag/views/node_e2e.py +54 -0
  391. dpone/services/dag/views/report.py +58 -0
  392. dpone/services/docs/__init__.py +21 -0
  393. dpone/services/docs/check_compatibility_service.py +81 -0
  394. dpone/services/docs/check_docs_service.py +82 -0
  395. dpone/services/docs/check_import_rules_service.py +44 -0
  396. dpone/services/docs/check_layer_metrics_service.py +149 -0
  397. dpone/services/docs/check_removal_readiness_service.py +79 -0
  398. dpone/services/docs/cli_reference.py +161 -0
  399. dpone/services/docs/compatibility_registry.py +235 -0
  400. dpone/services/docs/deprecation_roadmap.py +124 -0
  401. dpone/services/docs/generated_block.py +48 -0
  402. dpone/services/docs/markdown_links.py +242 -0
  403. dpone/services/docs/shim_removal_plan.py +234 -0
  404. dpone/services/docs/update_cli_reference_service.py +44 -0
  405. dpone/services/docs/update_deprecation_roadmap_service.py +53 -0
  406. dpone/services/docs/update_dev_metrics_service.py +145 -0
  407. dpone/services/docs/update_shim_removal_plan_service.py +59 -0
  408. dpone/services/manifest/__init__.py +19 -0
  409. dpone/services/manifest/load_context.py +115 -0
  410. dpone/services/manifest/views/__init__.py +24 -0
  411. dpone/services/manifest/views/common.py +34 -0
  412. dpone/services/manifest/views/explain.py +31 -0
  413. dpone/services/manifest/views/list.py +47 -0
  414. dpone/services/manifest/views/migrate.py +38 -0
  415. dpone/services/manifest/views/registry_lint.py +41 -0
  416. dpone/services/manifest/views/render.py +32 -0
  417. dpone/services/manifest/views/stats.py +30 -0
  418. dpone/services/manifest/views/validate.py +41 -0
  419. dpone/services/manifest/views/verify.py +22 -0
  420. dpone/sink/__init__.py +39 -0
  421. dpone/sink/base.py +6 -0
  422. dpone/sink/bigquery.py +6 -0
  423. dpone/sink/postgres.py +6 -0
  424. dpone/sink/staging.py +6 -0
  425. dpone/sink/strategies/__init__.py +6 -0
  426. dpone/sink/strategies/base.py +6 -0
  427. dpone/sink/strategies/bigquery/bigquery_base.py +6 -0
  428. dpone/sink/strategies/bigquery/bigquery_full_refresh.py +6 -0
  429. dpone/sink/strategies/bigquery/bigquery_increment_append.py +6 -0
  430. dpone/sink/strategies/bigquery/bigquery_increment_merge.py +6 -0
  431. dpone/sink/strategies/bigquery/bigquery_replace_strategy.py +6 -0
  432. dpone/sink/strategies/postgres/postgres_base.py +6 -0
  433. dpone/sink/strategies/postgres/postgres_full_refresh.py +6 -0
  434. dpone/sink/strategies/postgres/postgres_increment_append.py +6 -0
  435. dpone/sink/strategies/postgres/postgres_increment_merge.py +6 -0
  436. dpone/sink/strategies/postgres/postgres_replace_strategy.py +6 -0
  437. dpone/source/__init__.py +39 -0
  438. dpone/source/api/__init__.py +6 -0
  439. dpone/source/api/base.py +6 -0
  440. dpone/source/api/cbr.py +6 -0
  441. dpone/source/api/google_ads.py +6 -0
  442. dpone/source/api/google_sheets.py +6 -0
  443. dpone/source/api/omnidesk.py +6 -0
  444. dpone/source/api/openexchangerates.py +6 -0
  445. dpone/source/api/yandex_webmaster.py +6 -0
  446. dpone/source/base.py +6 -0
  447. dpone/source/clickhouse.py +6 -0
  448. dpone/source/postgres.py +6 -0
  449. dpone/source/strategies/__init__.py +6 -0
  450. dpone/source/strategies/api/__init__.py +6 -0
  451. dpone/source/strategies/api/base.py +6 -0
  452. dpone/source/strategies/api/cbr/__init__.py +6 -0
  453. dpone/source/strategies/api/cbr/full_extract.py +6 -0
  454. dpone/source/strategies/api/cbr/incremental_merge_extract.py +6 -0
  455. dpone/source/strategies/api/google_ads/__init__.py +6 -0
  456. dpone/source/strategies/api/google_ads/full_extract.py +6 -0
  457. dpone/source/strategies/api/google_ads/incremental_merge_extract.py +6 -0
  458. dpone/source/strategies/api/google_ads/specs.py +6 -0
  459. dpone/source/strategies/api/google_sheets/__init__.py +6 -0
  460. dpone/source/strategies/api/google_sheets/full_extract.py +6 -0
  461. dpone/source/strategies/api/omnidesk/__init__.py +6 -0
  462. dpone/source/strategies/api/omnidesk/dictionary_extract.py +6 -0
  463. dpone/source/strategies/api/omnidesk/full_extract.py +6 -0
  464. dpone/source/strategies/api/omnidesk/incremental_append_extract.py +6 -0
  465. dpone/source/strategies/api/omnidesk/incremental_merge_extract.py +6 -0
  466. dpone/source/strategies/api/yandex_webmaster/__init__.py +6 -0
  467. dpone/source/strategies/api/yandex_webmaster/common.py +6 -0
  468. dpone/source/strategies/api/yandex_webmaster/full_extract.py +6 -0
  469. dpone/source/strategies/api/yandex_webmaster/incremental_merge_extract.py +6 -0
  470. dpone/source/strategies/api/yandex_webmaster/replace_extract.py +6 -0
  471. dpone/source/strategies/base.py +6 -0
  472. dpone/source/strategies/clickhouse/__init__.py +6 -0
  473. dpone/source/strategies/clickhouse/clickhouse_base.py +6 -0
  474. dpone/source/strategies/clickhouse/clickhouse_full_extract.py +6 -0
  475. dpone/source/strategies/clickhouse/clickhouse_incremental_extract.py +6 -0
  476. dpone/source/strategies/clickhouse/incremental_state_manager.py +6 -0
  477. dpone/source/strategies/postgres/__init__.py +6 -0
  478. dpone/source/strategies/postgres/postgres_base.py +6 -0
  479. dpone/source/strategies/postgres/postgres_full_extract.py +6 -0
  480. dpone/source/strategies/postgres/postgres_incremental_extract.py +6 -0
  481. dpone/source/strategies/postgres/postgres_xmin_extract.py +6 -0
  482. dpone/sql_helpers/__init__.py +39 -0
  483. dpone/sql_helpers/exchange_queries.py +6 -0
  484. dpone/sql_helpers/reconciliation_queries.py +6 -0
  485. dpone/sql_helpers/run_state_queries.py +6 -0
  486. dpone/sql_helpers/technical_columns_queries.py +6 -0
  487. dpone/sql_helpers/xmin_queries.py +6 -0
  488. dpone/state/__init__.py +39 -0
  489. dpone/state/factory.py +6 -0
  490. dpone/state/repository.py +6 -0
  491. dpone/state/run_state.py +6 -0
  492. dpone/state/xmin_storage.py +6 -0
  493. dpone/xmin/__init__.py +39 -0
  494. dpone/xmin/manager.py +6 -0
  495. dpone/yaml_config_handler/__init__.py +42 -0
  496. dpone/yaml_config_handler/config.py +9 -0
  497. dpone/yaml_config_handler/dag_report.py +9 -0
  498. dpone/yaml_config_handler/deps_end_to_end_explain.py +9 -0
  499. dpone/yaml_config_handler/edge_end_to_end_explain.py +9 -0
  500. dpone/yaml_config_handler/edge_explain.py +9 -0
  501. dpone/yaml_config_handler/finder.py +9 -0
  502. dpone/yaml_config_handler/graph.py +9 -0
  503. dpone/yaml_config_handler/loader.py +9 -0
  504. dpone/yaml_config_handler/manager.py +9 -0
  505. dpone/yaml_config_handler/node_end_to_end_explain.py +9 -0
  506. dpone/yaml_config_handler/node_explain.py +9 -0
  507. dpone/yaml_config_handler/parse_trace.py +9 -0
  508. dpone/yaml_config_handler/post_parse_explain.py +9 -0
  509. dpone/yaml_config_handler/process.py +9 -0
  510. dpone/yaml_config_handler/resolver.py +9 -0
  511. dpone/yaml_config_handler/subgraph.py +9 -0
  512. dpone/yaml_config_handler/yaml_types.py +9 -0
  513. dpone-0.1.0.dist-info/METADATA +290 -0
  514. dpone-0.1.0.dist-info/RECORD +516 -0
  515. dpone-0.1.0.dist-info/WHEEL +4 -0
  516. 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
@@ -0,0 +1,7 @@
1
+ """dpone CLI.
2
+
3
+ The CLI is primarily a UX/debugging aid:
4
+ - list processes in a manifest (single or batch)
5
+ - render the effective process config for a selector
6
+ - validate naming/metadata conventions in CI
7
+ """
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,3 @@
1
+ """DAG CLI renderers."""
2
+
3
+ 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)