coltrace 0.1.0.dev7__tar.gz → 0.1.0.dev9__tar.gz

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 (152) hide show
  1. {coltrace-0.1.0.dev7/src/coltrace.egg-info → coltrace-0.1.0.dev9}/PKG-INFO +15 -7
  2. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/README.md +14 -6
  3. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/backlog.md +13 -1
  4. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/compatibility-checkpoint.md +254 -159
  5. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/decisions.md +13 -0
  6. coltrace-0.1.0.dev9/docs/extraction-architectures.md +66 -0
  7. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/lineage-json.md +25 -4
  8. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/supported-patterns.md +113 -0
  9. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/troubleshooting.md +31 -1
  10. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/README.md +39 -33
  11. coltrace-0.1.0.dev9/examples/databricks_project/pipeline_a/extract/quality_extract.py +43 -0
  12. coltrace-0.1.0.dev9/examples/databricks_project/pipeline_a/main_orchestrator.py +44 -0
  13. coltrace-0.1.0.dev9/examples/databricks_project/pipeline_a/transform/combine_sources.py +9 -0
  14. coltrace-0.1.0.dev9/examples/databricks_project/shared/topic_changes_extract.py +30 -0
  15. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/notebook_cell.py +33 -6
  16. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/pyproject.toml +54 -54
  17. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/__init__.py +22 -22
  18. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/analysis.py +1036 -58
  19. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/cli.py +7 -1
  20. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/compatibility.py +77 -18
  21. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/exporters.py +717 -711
  22. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/graph.py +500 -386
  23. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/journey.py +18 -0
  24. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/lineage_models.py +122 -115
  25. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/models.py +92 -92
  26. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/scanner.py +20 -2
  27. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/source_readers.py +85 -83
  28. coltrace-0.1.0.dev9/src/coltrace/sql_sources.py +669 -0
  29. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9/src/coltrace.egg-info}/PKG-INFO +15 -7
  30. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/SOURCES.txt +7 -0
  31. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_database_sources.py +296 -287
  32. coltrace-0.1.0.dev9/tests/integration/test_databricks_example.py +321 -0
  33. coltrace-0.1.0.dev9/tests/integration/test_extraction_architectures.py +277 -0
  34. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_notebook_reporting.py +42 -0
  35. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_project_compatibility.py +458 -458
  36. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_public_project_patterns.py +7 -2
  37. coltrace-0.1.0.dev9/tests/integration/test_result_containers.py +222 -0
  38. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_sql_reader_boundaries.py +321 -300
  39. coltrace-0.1.0.dev9/tests/integration/test_sql_templates.py +310 -0
  40. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_exporters.py +286 -286
  41. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_source_models.py +283 -283
  42. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_source_presentation.py +118 -116
  43. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_sql_sources.py +453 -350
  44. coltrace-0.1.0.dev7/examples/databricks_project/pipeline_a/main_orchestrator.py +0 -25
  45. coltrace-0.1.0.dev7/src/coltrace/sql_sources.py +0 -355
  46. coltrace-0.1.0.dev7/tests/integration/test_databricks_example.py +0 -131
  47. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/LICENSE +0 -0
  48. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/MANIFEST.in +0 -0
  49. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/accuracy.md +0 -0
  50. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/architecture.md +0 -0
  51. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/column-history.md +0 -0
  52. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/column-journey-checkpoint.md +0 -0
  53. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/database-patterns.md +0 -0
  54. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/database-sources.md +0 -0
  55. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/discovery-json.md +0 -0
  56. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/graph-workspace-checkpoint.md +0 -0
  57. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/history-checkpoint.md +0 -0
  58. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/mvp-checkpoint.md +0 -0
  59. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/nlp-checkpoint.md +0 -0
  60. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/other-column-checkpoint.md +0 -0
  61. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/pandas-sources-proposal.md +0 -0
  62. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/phase-1.md +0 -0
  63. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/read-capture.md +0 -0
  64. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/source-checkpoint.md +0 -0
  65. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/README.md +0 -0
  66. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/build_query.py +0 -0
  67. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/pipeline.py +0 -0
  68. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/pyproject.toml +0 -0
  69. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/main_orchestrator.py +0 -0
  70. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/__init__.py +0 -0
  71. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/extract/__init__.py +0 -0
  72. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/extract/primary_extract.py +0 -0
  73. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/publish/__init__.py +0 -0
  74. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/publish/serialize_webapp.py +0 -0
  75. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/transform/__init__.py +0 -0
  76. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/transform/priority_rank.py +0 -0
  77. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/__init__.py +0 -0
  78. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/config.py +0 -0
  79. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/token_tracker.py +0 -0
  80. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/warehouse.py +0 -0
  81. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/extract/prices.py +0 -0
  82. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/features/pricing.py +0 -0
  83. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/pipeline.py +0 -0
  84. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/transform/pricing.py +0 -0
  85. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/README.md +0 -0
  86. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/project/input.csv +0 -0
  87. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/project/pipeline.py +0 -0
  88. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/run_capture.py +0 -0
  89. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/README.md +0 -0
  90. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/extract.py +0 -0
  91. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/pipeline.py +0 -0
  92. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/transform.py +0 -0
  93. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/sample_pipeline/prices.py +0 -0
  94. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/README.md +0 -0
  95. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/pipeline.py +0 -0
  96. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/processing.py +0 -0
  97. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/readers.py +0 -0
  98. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/text.py +0 -0
  99. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/setup.cfg +0 -0
  100. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/__main__.py +0 -0
  101. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/callbacks.py +0 -0
  102. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/capture.py +0 -0
  103. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/collector.py +0 -0
  104. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/history.py +0 -0
  105. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/nosql_sources.py +0 -0
  106. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/notebooks.py +0 -0
  107. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/pandas_api.py +0 -0
  108. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/py.typed +0 -0
  109. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/source_models.py +0 -0
  110. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/templates/graph.html +0 -0
  111. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/visual_layout.py +0 -0
  112. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/dependency_links.txt +0 -0
  113. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/entry_points.txt +0 -0
  114. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/requires.txt +0 -0
  115. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/top_level.txt +0 -0
  116. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/extract/prices.py +0 -0
  117. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/features/pricing.py +0 -0
  118. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/pipeline.py +0 -0
  119. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/transform/pricing.py +0 -0
  120. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_annotation_effects.py +0 -0
  121. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_callback_roles.py +0 -0
  122. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_capture_workflow.py +0 -0
  123. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_cli.py +0 -0
  124. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_column_story.py +0 -0
  125. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_compatibility_cli.py +0 -0
  126. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_conditional_flow.py +0 -0
  127. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_journey_boundaries.py +0 -0
  128. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_lineage_acceptance.py +0 -0
  129. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_nosql_extraction.py +0 -0
  130. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_other_column_stories.py +0 -0
  131. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_pandas_boundaries.py +0 -0
  132. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_pandas_observation.py +0 -0
  133. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_production_patterns.py +0 -0
  134. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_reader_inventory.py +0 -0
  135. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_resolution_boundaries.py +0 -0
  136. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_scan_project.py +0 -0
  137. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_source_config.py +0 -0
  138. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_source_precision.py +0 -0
  139. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_text_pipeline.py +0 -0
  140. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_analysis.py +0 -0
  141. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_callbacks.py +0 -0
  142. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_collector.py +0 -0
  143. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_compatibility.py +0 -0
  144. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_graph.py +0 -0
  145. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_graph_workspace.py +0 -0
  146. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_history.py +0 -0
  147. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_journey.py +0 -0
  148. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_nosql_sources.py +0 -0
  149. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_notebooks.py +0 -0
  150. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_read_capture.py +0 -0
  151. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_scanner.py +0 -0
  152. {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_visual_layout.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coltrace
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev9
4
4
  Summary: Static Pandas column lineage, impact analysis, and local dependency graphs
5
5
  Author: Cristiano Diniz
6
6
  License-Expression: MIT
@@ -32,7 +32,7 @@ column versions across direct function calls. Static scanning never imports or
32
32
  executes the project or reads its input data. Optional read capture observes
33
33
  column labels while you explicitly execute your own pipeline.
34
34
 
35
- **Development preview: `0.1.0.dev7`.** The supported subset is deliberately small.
35
+ **Development preview: `0.1.0.dev9`.** The supported subset is deliberately small.
36
36
  Unsupported operations produce diagnostics and may interrupt lineage. A graph
37
37
  with no edge is not proof that a dependency does not exist.
38
38
 
@@ -58,10 +58,16 @@ separate release step; these commands install the local project.
58
58
 
59
59
  The source checkpoint recognizes supported SQL-to-Pandas and NoSQL-to-Pandas
60
60
  extractions. Literal SQL projections, aliases and expressions describe source
61
- fields before their first Python use. Query filters are control metadata rather
62
- than value dependencies of every output. The supplied
63
- `spark.sql(build_query(table)).toPandas()` pattern works with a resolved Spark
64
- session or an explicit source contract for an injected session.
61
+ fields before their first Python use. Every database column the SQL reads is a
62
+ selectable lineage node (`origin="table"`), so a column used only inside
63
+ `SUM(...)` or a `WHERE` clause can be traced to the DataFrame columns it feeds.
64
+ Query filters are control metadata rather than value dependencies of every
65
+ output. `spark.sql(build_query(table)).toPandas()` keeps its SQL text even with
66
+ an injected, unresolved session; SQL assembled from f-strings, `.format`, `%`
67
+ and joins keeps its known text, with unknown parts shown as `{expression}`.
68
+ `SELECT *`, `UNION`, tables read by name (`spark.table`, Snowpark
69
+ `session.table`, `dbt.ref`) and SQL files read inside the project are modeled
70
+ too; see [extraction architectures](docs/extraction-architectures.md).
65
71
 
66
72
  `report.readers` inventories resolved `pd.read_*` calls with file, line, context,
67
73
  return shape and resulting DataFrame identity. File schemas are unknown unless
@@ -221,7 +227,7 @@ the shortest eligible distance, so a node reached both ways appears as direct.
221
227
  | --- | --- |
222
228
  | Explicit Pandas imports and aliases | Recognized origins; names such as `df` alone are insufficient |
223
229
  | Resolved `pd.read_*` calls | Located inventory; supported DataFrame readers plus explicit/observed source schemas; iterators/collections remain distinct |
224
- | SQL-to-Pandas extraction | Bounded SQL projections, aliases, expressions and helpers; documented readers/conversions or explicit contracts |
230
+ | SQL-to-Pandas extraction | Bounded SQL projections, aliases, expressions and helpers; documented readers/conversions or explicit contracts; database columns as nodes; dialect fallback |
225
231
  | MongoDB / DynamoDB extraction | Literal projections and bounded Mongo expressions; candidate fields stay partial without observed schema |
226
232
  | Literal dictionary `DataFrame` construction | String column keys and literal values |
227
233
  | `frame["column"]`, `frame.loc[:, "column"]` | Literal or statically known labels (`for c in COLS`, f-strings) |
@@ -308,6 +314,8 @@ tox/nox and pytest/mypy/Ruff caches, `build`, and `dist`. Linked files/directori
308
314
  and Windows reparse points are skipped. A `.coltraceignore` file at the analyzed
309
315
  root uses gitignore syntax (root-level subset, including `!` negation). The root
310
316
  `.gitignore` is also applied unless `respect_gitignore = false` is set.
317
+ `sql_dialect = "bigquery"` names the dialect of SQL text whose reader does not;
318
+ text that does not parse is still retried in common dialects and reported.
311
319
  `observers = ["notify_team", "client.*"]` declares project helpers that only read
312
320
  the DataFrames they receive, so they do not interrupt lineage.
313
321
  `ScanConfig(exclude=("generated",))` overrides TOML settings and extends built-ins.
@@ -6,7 +6,7 @@ column versions across direct function calls. Static scanning never imports or
6
6
  executes the project or reads its input data. Optional read capture observes
7
7
  column labels while you explicitly execute your own pipeline.
8
8
 
9
- **Development preview: `0.1.0.dev7`.** The supported subset is deliberately small.
9
+ **Development preview: `0.1.0.dev9`.** The supported subset is deliberately small.
10
10
  Unsupported operations produce diagnostics and may interrupt lineage. A graph
11
11
  with no edge is not proof that a dependency does not exist.
12
12
 
@@ -32,10 +32,16 @@ separate release step; these commands install the local project.
32
32
 
33
33
  The source checkpoint recognizes supported SQL-to-Pandas and NoSQL-to-Pandas
34
34
  extractions. Literal SQL projections, aliases and expressions describe source
35
- fields before their first Python use. Query filters are control metadata rather
36
- than value dependencies of every output. The supplied
37
- `spark.sql(build_query(table)).toPandas()` pattern works with a resolved Spark
38
- session or an explicit source contract for an injected session.
35
+ fields before their first Python use. Every database column the SQL reads is a
36
+ selectable lineage node (`origin="table"`), so a column used only inside
37
+ `SUM(...)` or a `WHERE` clause can be traced to the DataFrame columns it feeds.
38
+ Query filters are control metadata rather than value dependencies of every
39
+ output. `spark.sql(build_query(table)).toPandas()` keeps its SQL text even with
40
+ an injected, unresolved session; SQL assembled from f-strings, `.format`, `%`
41
+ and joins keeps its known text, with unknown parts shown as `{expression}`.
42
+ `SELECT *`, `UNION`, tables read by name (`spark.table`, Snowpark
43
+ `session.table`, `dbt.ref`) and SQL files read inside the project are modeled
44
+ too; see [extraction architectures](docs/extraction-architectures.md).
39
45
 
40
46
  `report.readers` inventories resolved `pd.read_*` calls with file, line, context,
41
47
  return shape and resulting DataFrame identity. File schemas are unknown unless
@@ -195,7 +201,7 @@ the shortest eligible distance, so a node reached both ways appears as direct.
195
201
  | --- | --- |
196
202
  | Explicit Pandas imports and aliases | Recognized origins; names such as `df` alone are insufficient |
197
203
  | Resolved `pd.read_*` calls | Located inventory; supported DataFrame readers plus explicit/observed source schemas; iterators/collections remain distinct |
198
- | SQL-to-Pandas extraction | Bounded SQL projections, aliases, expressions and helpers; documented readers/conversions or explicit contracts |
204
+ | SQL-to-Pandas extraction | Bounded SQL projections, aliases, expressions and helpers; documented readers/conversions or explicit contracts; database columns as nodes; dialect fallback |
199
205
  | MongoDB / DynamoDB extraction | Literal projections and bounded Mongo expressions; candidate fields stay partial without observed schema |
200
206
  | Literal dictionary `DataFrame` construction | String column keys and literal values |
201
207
  | `frame["column"]`, `frame.loc[:, "column"]` | Literal or statically known labels (`for c in COLS`, f-strings) |
@@ -282,6 +288,8 @@ tox/nox and pytest/mypy/Ruff caches, `build`, and `dist`. Linked files/directori
282
288
  and Windows reparse points are skipped. A `.coltraceignore` file at the analyzed
283
289
  root uses gitignore syntax (root-level subset, including `!` negation). The root
284
290
  `.gitignore` is also applied unless `respect_gitignore = false` is set.
291
+ `sql_dialect = "bigquery"` names the dialect of SQL text whose reader does not;
292
+ text that does not parse is still retried in common dialects and reported.
285
293
  `observers = ["notify_team", "client.*"]` declares project helpers that only read
286
294
  the DataFrames they receive, so they do not interrupt lineage.
287
295
  `ScanConfig(exclude=("generated",))` overrides TOML settings and extends built-ins.
@@ -64,11 +64,23 @@ not a capability of the published release.
64
64
  | CT-098 | Reshaping (pivot, melt, get_dummies) | Implemented dev7 with open derived schemas; stack/unstack/crosstab pending |
65
65
  | CT-099 | Observing libraries and declared observers | Implemented dev7 |
66
66
  | CT-100 | Native `.ipynb` input | Proposed: notebooks were converted outside ColTrace for these tests |
67
+ | CT-101 | Database columns as nodes | Implemented dev8 (schema 0.6): `table` frames, pass/rename/derive from SQL fields, controls as filter consumption |
68
+ | CT-102 | Runtime-assembled SQL | Implemented dev8: labeled placeholders, `%`/join folding, comment-led SQL, fragment recovery |
69
+ | CT-103 | SQL dialect fallback and `sql_dialect` | Implemented dev8 |
70
+ | CT-104 | Conversion keeps receiver SQL (`spark.sql(...).toPandas()`) | Implemented dev8 |
71
+ | CT-105 | Ranked candidates for ambiguous names | Implemented dev8: `LineageGraph.candidates`, notebook `ON_AMBIGUOUS` |
72
+ | CT-106 | Cursor `execute` then `fetch*` on separate statements | Proposed: remember the last SQL text executed on a resolved cursor name |
73
+ | CT-107 | Jinja/dbt templates (`{{ ref() }}`) | Proposed: out of scope until a real project needs it |
74
+ | CT-108 | Dict entries, exact unrolling, accumulators | Implemented dev9 |
75
+ | CT-109 | Filters along the call chain | Implemented dev9 |
76
+ | CT-110 | Open `SELECT *` schemas, UNION, tables by name, SQL files | Implemented dev9 |
77
+ | CT-111 | Extraction census tool | Done in the session scratchpad; promote to `tools/` if it becomes a regular check |
78
+ | CT-112 | `%%bigquery df` cell magic as a SQL source | Proposed (Jupyter/Colab users) |
67
79
  | CT-095 | Lazy materialization | Proposed: avoid eager pass copies of every known column on each row operation for very wide frames |
68
80
 
69
81
  ## Next checkpoint
70
82
 
71
- 1. Rerun the internal compatibility report with dev7 and classify the first remaining identity break for the target column.
83
+ 1. Rerun the internal compatibility report with dev8: select the target column (now a database column if it is only read inside SQL) and classify the first remaining break.
72
84
  2. Inspect the generated HTML's pan/zoom/selection behavior in a normal browser.
73
85
  3. Run ColTrace against a small real pipeline with a manually checked expected graph.
74
86
  4. Classify missing/incorrect relations by semantic rule, using [accuracy gates](accuracy.md).
@@ -1,159 +1,254 @@
1
- # Project compatibility checkpoint
2
-
3
- Local checkpoint: `0.1.0.dev7`, 2026-11-03. Publication is a separate step.
4
- This work responds to an anonymized compatibility report produced inside the
5
- user's company. The private project was not accessed, copied or executed; every
6
- fixture below is a new synthetic reproduction of a reported technical shape.
7
-
8
- ## What the report showed (dev6)
9
-
10
- On a 58-file Databricks-style project, dev6 parsed 57 files and produced 32
11
- frames, 11 column versions, 0 sources, 4 edges and 1,612 diagnostics. The
12
- requested business column was absent. The leading causes were:
13
-
14
- | Report finding | Root cause in dev6 |
15
- | --- | --- |
16
- | `CT_SYNTAX_ERROR` on the entrypoint | Raw `%pip` magics made the orchestrator notebook unparseable; `%run` chains were invisible |
17
- | 0 sources | Data entered through an injected `run_query(sql)` closure; nested functions were never resolved |
18
- | 478 `CT_UNSUPPORTED_EXPRESSION` | `&`/`|`, `and`/`or`, ternaries, comprehensions and slices invalidated reachable frames |
19
- | 270 `CT_CONDITIONAL_FLOW`, 36 `CT_CONDITIONAL_RETURN` | Every frame mutated in a branch became a boundary; `if df.empty: return df` erased the result identity |
20
- | 258 `CT_ANALYSIS_LIMIT` | Path and call budgets were counted for the whole project, not per function or module |
21
- | 72 `CT_OPAQUE_CALL`, 287 `CT_UNRESOLVED_RECEIVER` | Common methods (`fillna`, `astype`, `sort_values`, `np.where`, …) invalidated the frame; dict writes were reported as DataFrame gaps |
22
- | 21 `CT_UNSUPPORTED_CONTROL_FLOW`, 17 `CT_UNSUPPORTED_FUNCTION` | `class`, `+=`, `assert`, nested and decorated functions invalidated state |
23
- | 9 `CT_DYNAMIC_COLUMN`, 36 `CT_UNSUPPORTED_TARGET` | `for c in COLS: df[c] = …` and tuple unpacking were not modeled |
24
-
25
- ## Corrected behavior (dev7)
26
-
27
- - **Notebook source.** Databricks `.py` exports and raw IPython magics are
28
- prepared in place: magic lines become inert statements with unchanged line
29
- numbers, magic-looking text inside strings is preserved, and `# MAGIC %run ./x`
30
- or raw `%run` includes the target notebook statically in the caller's
31
- namespace. Included notebooks are not analyzed a second time standalone.
32
- - **Per-unit budgets.** Each module and each uncalled-function template has its
33
- own path and call budget.
34
- - **Joins.** After `if`/`try`/loops, a column whose version differs between
35
- alternatives receives an inferred merge version reading every possible value.
36
- Untouched columns keep their identity. Early returns are joined at the function
37
- exit; different frame alternatives produce a new inferred `merge` frame whose
38
- columns may come from any alternative. Invalidated alternatives remain
39
- boundaries. Merged facts are never `confirmed`.
40
- - **Expressions.** Bitwise masks, boolean operators, ternaries, comparisons,
41
- slices, comprehensions, walrus and starred expressions are interpreted. An
42
- operand of unknown provenance makes the result `inferred`/`partial` instead of
43
- cutting the dependency on tracked columns. Constant folding covers literal
44
- arithmetic, known dicts (`{**DEFAULTS, **params}`), known builtins and string
45
- methods.
46
- - **Pandas/NumPy catalog.** Documented Series methods (masks, aggregates,
47
- elementwise transforms, row selection), `.str`/`.dt`/`.cat` accessors,
48
- NumPy elementwise functions and `pd.to_numeric`-style functions derive values.
49
- DataFrame `sort_values`, `head`, `reset_index`, `drop(columns=…)`, `query`,
50
- `set_index`, `fillna`/`astype` (including per-column dicts), `insert`, `pop`,
51
- `merge`/`join`/`concat` (inferred), partial `.loc[mask, col] = …` updates and
52
- more exports are modeled. Unlisted DataFrame summaries such as `groupby`
53
- leave the receiver intact and report `CT_UNMODELED_RESULT`.
54
- - **Python structures.** Classes (methods, instance attributes, dataclass-like
55
- fields), nested functions with closures, decorated functions (assumed
56
- transparent, inferred), `*args/**kwargs`, `+=`, `assert`, `del`, `match`,
57
- fixed-width unpacking and loops over literal or configured label collections
58
- (unrolled exactly) are interpreted.
59
- - **Injected sources.** Nested closures such as `run_query` are followed into
60
- `client.query(sql).to_dataframe()`. When an unresolved call result is used
61
- with a DataFrame-only API, it becomes an inferred frame of unresolved origin;
62
- if its first argument is SQL text, the SELECT list seeds its columns.
63
- Interpolations that cannot be resolved become placeholders and are excluded
64
- from fields. Source contracts may now name an injected parameter explicitly.
65
- - **Noise.** Writes to mappings such as `os.environ` or config dicts are no
66
- longer reported as DataFrame gaps; parameters and DataFrame-used names still
67
- are. Inferences and assumptions are `info` diagnostics, distinct from warnings.
68
- - **Inputs.** `.coltraceignore` and the root `.gitignore` are applied, and the
69
- notebook cell writes artifacts outside the analyzed tree by default.
70
-
71
- ## Measured on the synthetic reproduction
72
-
73
- [`examples/databricks_project`](../examples/databricks_project/README.md)
74
- reproduces the report's structure in 13 files. Results on the same input:
75
-
76
- | Metric | dev6 | dev7 |
77
- | --- | ---: | ---: |
78
- | Files parsed | 12 of 13 | 13 of 13 |
79
- | DataFrames / column versions | 0 / 0 | 10 / 88 |
80
- | Sources | 0 | 1 SQL (6 fields) |
81
- | Warnings / errors | 32 / 1 | 0 / 0 |
82
- | `n_contacts → priority_score → published_score` | not found | found, with exports |
83
-
84
- Its acceptance test lists expected relations and forbidden ones (row-filter and
85
- sort-key columns must not become value inputs). A generated 60-module
86
- project with ~240 functions, branches, loops, classes and merges is analyzed in
87
- about 5 seconds locally. These numbers are engineering checks on synthetic
88
- input, not measured accuracy on the private project.
89
-
90
- ## Validation
91
-
92
- 941 tests pass (67 more than dev6, with dev6 join tests rewritten to require
93
- inferred-only paths); Ruff lint/format and strict mypy pass. Lineage snapshots are schema `0.5` (adds the `info` severity); `0.4`
94
- snapshots load unchanged.
95
-
96
- ```sh
97
- python -m pytest -q
98
- python -m ruff check src tests examples
99
- python -m ruff format --check src tests examples
100
- python -m mypy
101
- python -m coltrace scan examples/databricks_project --out .coltrace/databricks
102
- python -m coltrace trace n_contacts --from .coltrace/databricks/scan.json
103
- ```
104
-
105
- ## Second round: public projects
106
-
107
- Before publishing, ColTrace was run statically (nothing executed) over 14
108
- public projects, about 1,200 files: the Kedro spaceflights starter, the
109
- DataTalksClub MLOps and Data Engineering Zoomcamps, four Databricks solution
110
- accelerators, the recommenders library, the owid/covid-19-data production ETL
111
- scripts, and two data science books whose notebooks were converted to `.py`
112
- outside ColTrace. Every report had to survive a JSON snapshot round trip.
113
-
114
- | Project | Files | Seconds | Column versions | Warnings | Errors |
115
- | --- | ---: | ---: | ---: | ---: | ---: |
116
- | Kedro spaceflights | 14 | crash → 0.0 | → 39 | → 2 | → 0 |
117
- | MLOps Zoomcamp | 88 | 2.9 → 1.2 | 633 → 2,613 | 1,637 → 41 | 0 → 0 |
118
- | Data Engineering Zoomcamp | 72 | crash → 0.6 | → 32 | → 25 | → 0 |
119
- | Databricks CLV | 2 | 0.2 → 0.1 | 11 → 40 | 28 → 8 | 0 → 0 |
120
- | Databricks demand forecasting | 4 | 0.5 → 0.4 | 35 → 46 | 55 → 8 | 0 → 0 |
121
- | Databricks propensity | 10 | 1.8 → 3.6 | 32 → 7 | 72 → 25 | 0 → 0 |
122
- | Databricks safety stock | 3 | 0.1 → 0.0 | 59 → 59 | 19 → 0 | 0 → 0 |
123
- | Hands-on ML notebooks | 27 | 8.5 → 6.3 | 203 → 344 | 5,173 → 158 | 1 → 0 |
124
- | Data Science Handbook notebooks | 136 | 5.0 → 3.0 | 350 → 606 | 898 → 209 | 3 → 2 |
125
- | MLOps notebooks | 26 | 0.8 → 0.3 | 282 → 827 | 375 → 9 | 1 → 0 |
126
- | recommenders notebooks | 62 | 2.6 → 1.0 | 396 → 569 | 564 → 214 | 3 → 2 |
127
- | owid/covid-19-data scripts | 450 | 148.4 → 39.8 | 8,119 → 66,334 | 8,931 → 8,129 | 0 → 0 |
128
- | recommenders library | 249 | 52.0 → 11.9 | 1,654 → 3,394 | 6,075 → 1,561 | 0 → 0 |
129
-
130
- The first column of each pair is the first dev7 build, the second the final
131
- one. Column counts in the propensity notebooks fell because PySpark DataFrames
132
- were previously mistaken for Pandas frames. Remaining errors are genuine syntax
133
- errors in the source (a missing comma, deliberately invalid book examples).
134
- The owid scripts now resolve their `src/` package, so far more of their code is
135
- interpreted, which explains both the larger counts and the remaining warnings.
136
-
137
- Hand-written expectations for the Kedro nodes and two MLOps scripts: 14 of 14
138
- expected relations found and 0 of 9 forbidden relations reported. Defects fixed
139
- in this round included an analysis crash on Windows paths longer than 260
140
- characters, duplicated frame/reader/column identities that made most real
141
- snapshots unloadable, project-wide state copying on every branch, and analyzed
142
- code's `SyntaxWarning` output. The covered patterns are listed in
143
- [supported patterns](supported-patterns.md#public-project-patterns-dev7-second-round).
144
-
145
- ## Remaining boundaries
146
-
147
- This is still bounded abstract interpretation, not a general control-flow solver.
148
- Loops over unknown collections are summarized by one possible iteration joined
149
- with zero iterations. `groupby(...).apply`, `stack`/`unstack`, `crosstab` and
150
- row-wise callbacks with side effects remain unmodeled results. Calls to unknown
151
- helpers that receive a DataFrame still interrupt its provenance unless declared
152
- as `observers`. Merge suffixes and
153
- row multiplicity are not resolved. Async functions, reflection, `global`/`nonlocal`
154
- writes and properties remain unresolved. `.ipynb` JSON files, live cells and
155
- `dbutils.notebook.run` (a separate namespace) are not inputs. Opaque calls that
156
- receive a DataFrame still interrupt its provenance.
157
-
158
- The next acceptance input is a rerun of the internal compatibility report on
159
- dev7, identifying the first remaining identity break for the target column.
1
+ # Project compatibility checkpoint
2
+
3
+ Local checkpoint: `0.1.0.dev9`, 2026-11-04 (dev7 and dev8 sections below are
4
+ kept as history). Publication is a separate step.
5
+ This work responds to an anonymized compatibility report produced inside the
6
+ user's company. The private project was not accessed, copied or executed; every
7
+ fixture below is a new synthetic reproduction of a reported technical shape.
8
+
9
+ ## What the report showed (dev6)
10
+
11
+ On a 58-file Databricks-style project, dev6 parsed 57 files and produced 32
12
+ frames, 11 column versions, 0 sources, 4 edges and 1,612 diagnostics. The
13
+ requested business column was absent. The leading causes were:
14
+
15
+ | Report finding | Root cause in dev6 |
16
+ | --- | --- |
17
+ | `CT_SYNTAX_ERROR` on the entrypoint | Raw `%pip` magics made the orchestrator notebook unparseable; `%run` chains were invisible |
18
+ | 0 sources | Data entered through an injected `run_query(sql)` closure; nested functions were never resolved |
19
+ | 478 `CT_UNSUPPORTED_EXPRESSION` | `&`/`|`, `and`/`or`, ternaries, comprehensions and slices invalidated reachable frames |
20
+ | 270 `CT_CONDITIONAL_FLOW`, 36 `CT_CONDITIONAL_RETURN` | Every frame mutated in a branch became a boundary; `if df.empty: return df` erased the result identity |
21
+ | 258 `CT_ANALYSIS_LIMIT` | Path and call budgets were counted for the whole project, not per function or module |
22
+ | 72 `CT_OPAQUE_CALL`, 287 `CT_UNRESOLVED_RECEIVER` | Common methods (`fillna`, `astype`, `sort_values`, `np.where`, …) invalidated the frame; dict writes were reported as DataFrame gaps |
23
+ | 21 `CT_UNSUPPORTED_CONTROL_FLOW`, 17 `CT_UNSUPPORTED_FUNCTION` | `class`, `+=`, `assert`, nested and decorated functions invalidated state |
24
+ | 9 `CT_DYNAMIC_COLUMN`, 36 `CT_UNSUPPORTED_TARGET` | `for c in COLS: df[c] = …` and tuple unpacking were not modeled |
25
+
26
+ ## Corrected behavior (dev7)
27
+
28
+ - **Notebook source.** Databricks `.py` exports and raw IPython magics are
29
+ prepared in place: magic lines become inert statements with unchanged line
30
+ numbers, magic-looking text inside strings is preserved, and `# MAGIC %run ./x`
31
+ or raw `%run` includes the target notebook statically in the caller's
32
+ namespace. Included notebooks are not analyzed a second time standalone.
33
+ - **Per-unit budgets.** Each module and each uncalled-function template has its
34
+ own path and call budget.
35
+ - **Joins.** After `if`/`try`/loops, a column whose version differs between
36
+ alternatives receives an inferred merge version reading every possible value.
37
+ Untouched columns keep their identity. Early returns are joined at the function
38
+ exit; different frame alternatives produce a new inferred `merge` frame whose
39
+ columns may come from any alternative. Invalidated alternatives remain
40
+ boundaries. Merged facts are never `confirmed`.
41
+ - **Expressions.** Bitwise masks, boolean operators, ternaries, comparisons,
42
+ slices, comprehensions, walrus and starred expressions are interpreted. An
43
+ operand of unknown provenance makes the result `inferred`/`partial` instead of
44
+ cutting the dependency on tracked columns. Constant folding covers literal
45
+ arithmetic, known dicts (`{**DEFAULTS, **params}`), known builtins and string
46
+ methods.
47
+ - **Pandas/NumPy catalog.** Documented Series methods (masks, aggregates,
48
+ elementwise transforms, row selection), `.str`/`.dt`/`.cat` accessors,
49
+ NumPy elementwise functions and `pd.to_numeric`-style functions derive values.
50
+ DataFrame `sort_values`, `head`, `reset_index`, `drop(columns=…)`, `query`,
51
+ `set_index`, `fillna`/`astype` (including per-column dicts), `insert`, `pop`,
52
+ `merge`/`join`/`concat` (inferred), partial `.loc[mask, col] = …` updates and
53
+ more exports are modeled. Unlisted DataFrame summaries such as `groupby`
54
+ leave the receiver intact and report `CT_UNMODELED_RESULT`.
55
+ - **Python structures.** Classes (methods, instance attributes, dataclass-like
56
+ fields), nested functions with closures, decorated functions (assumed
57
+ transparent, inferred), `*args/**kwargs`, `+=`, `assert`, `del`, `match`,
58
+ fixed-width unpacking and loops over literal or configured label collections
59
+ (unrolled exactly) are interpreted.
60
+ - **Injected sources.** Nested closures such as `run_query` are followed into
61
+ `client.query(sql).to_dataframe()`. When an unresolved call result is used
62
+ with a DataFrame-only API, it becomes an inferred frame of unresolved origin;
63
+ if its first argument is SQL text, the SELECT list seeds its columns.
64
+ Interpolations that cannot be resolved become placeholders and are excluded
65
+ from fields. Source contracts may now name an injected parameter explicitly.
66
+ - **Noise.** Writes to mappings such as `os.environ` or config dicts are no
67
+ longer reported as DataFrame gaps; parameters and DataFrame-used names still
68
+ are. Inferences and assumptions are `info` diagnostics, distinct from warnings.
69
+ - **Inputs.** `.coltraceignore` and the root `.gitignore` are applied, and the
70
+ notebook cell writes artifacts outside the analyzed tree by default.
71
+
72
+ ## Measured on the synthetic reproduction
73
+
74
+ [`examples/databricks_project`](../examples/databricks_project/README.md)
75
+ reproduces the report's structure in 13 files. Results on the same input:
76
+
77
+ | Metric | dev6 | dev7 |
78
+ | --- | ---: | ---: |
79
+ | Files parsed | 12 of 13 | 13 of 13 |
80
+ | DataFrames / column versions | 0 / 0 | 10 / 88 |
81
+ | Sources | 0 | 1 SQL (6 fields) |
82
+ | Warnings / errors | 32 / 1 | 0 / 0 |
83
+ | `n_contacts → priority_score → published_score` | not found | found, with exports |
84
+
85
+ Its acceptance test lists expected relations and forbidden ones (row-filter and
86
+ sort-key columns must not become value inputs). A generated 60-module
87
+ project with ~240 functions, branches, loops, classes and merges is analyzed in
88
+ about 5 seconds locally. These numbers are engineering checks on synthetic
89
+ input, not measured accuracy on the private project.
90
+
91
+ ## Validation
92
+
93
+ dev9: 1,024 tests pass (984 in dev8; dev9 adds container, accumulator, filter
94
+ and extraction-shape tests). dev8: 984 (941 in dev7, plus the SQL boundary
95
+ tests; SQL control tests require database-table columns consumed as row
96
+ controls).
97
+ Ruff lint/format and strict mypy pass. Lineage snapshots are schema `0.6`
98
+ (adds `dataframes[].kind`); `0.4` and `0.5` snapshots load as DataFrame frames.
99
+
100
+ ```sh
101
+ python -m pytest -q
102
+ python -m ruff check src tests examples
103
+ python -m ruff format --check src tests examples
104
+ python -m mypy
105
+ python -m coltrace scan examples/databricks_project --out .coltrace/databricks
106
+ python -m coltrace trace n_contacts --from .coltrace/databricks/scan.json
107
+ python -m coltrace trace contact_attempts --from .coltrace/databricks/scan.json
108
+ ```
109
+
110
+ ## Second round: public projects
111
+
112
+ Before publishing, ColTrace was run statically (nothing executed) over 14
113
+ public projects, about 1,200 files: the Kedro spaceflights starter, the
114
+ DataTalksClub MLOps and Data Engineering Zoomcamps, four Databricks solution
115
+ accelerators, the recommenders library, the owid/covid-19-data production ETL
116
+ scripts, and two data science books whose notebooks were converted to `.py`
117
+ outside ColTrace. Every report had to survive a JSON snapshot round trip.
118
+
119
+ | Project | Files | Seconds | Column versions | Warnings | Errors |
120
+ | --- | ---: | ---: | ---: | ---: | ---: |
121
+ | Kedro spaceflights | 14 | crash → 0.0 | → 39 | → 2 | → 0 |
122
+ | MLOps Zoomcamp | 88 | 2.9 → 1.2 | 633 → 2,613 | 1,637 → 41 | 0 → 0 |
123
+ | Data Engineering Zoomcamp | 72 | crash → 0.6 | → 32 | → 25 | → 0 |
124
+ | Databricks CLV | 2 | 0.2 → 0.1 | 11 → 40 | 28 → 8 | 0 → 0 |
125
+ | Databricks demand forecasting | 4 | 0.5 → 0.4 | 35 → 46 | 55 → 8 | 0 → 0 |
126
+ | Databricks propensity | 10 | 1.8 → 3.6 | 32 → 7 | 72 → 25 | 0 → 0 |
127
+ | Databricks safety stock | 3 | 0.1 → 0.0 | 59 → 59 | 19 → 0 | 0 → 0 |
128
+ | Hands-on ML notebooks | 27 | 8.5 → 6.3 | 203 → 344 | 5,173 → 158 | 1 → 0 |
129
+ | Data Science Handbook notebooks | 136 | 5.0 → 3.0 | 350 → 606 | 898 → 209 | 3 → 2 |
130
+ | MLOps notebooks | 26 | 0.8 → 0.3 | 282 → 827 | 375 → 9 | 1 → 0 |
131
+ | recommenders notebooks | 62 | 2.6 → 1.0 | 396 → 569 | 564 → 214 | 3 → 2 |
132
+ | owid/covid-19-data scripts | 450 | 148.4 → 39.8 | 8,119 → 66,334 | 8,931 → 8,129 | 0 → 0 |
133
+ | recommenders library | 249 | 52.0 → 11.9 | 1,654 → 3,394 | 6,075 → 1,561 | 0 → 0 |
134
+
135
+ The first column of each pair is the first dev7 build, the second the final
136
+ one. Column counts in the propensity notebooks fell because PySpark DataFrames
137
+ were previously mistaken for Pandas frames. Remaining errors are genuine syntax
138
+ errors in the source (a missing comma, deliberately invalid book examples).
139
+ The owid scripts now resolve their `src/` package, so far more of their code is
140
+ interpreted, which explains both the larger counts and the remaining warnings.
141
+
142
+ Hand-written expectations for the Kedro nodes and two MLOps scripts: 14 of 14
143
+ expected relations found and 0 of 9 forbidden relations reported. Defects fixed
144
+ in this round included an analysis crash on Windows paths longer than 260
145
+ characters, duplicated frame/reader/column identities that made most real
146
+ snapshots unloadable, project-wide state copying on every branch, and analyzed
147
+ code's `SyntaxWarning` output. The covered patterns are listed in
148
+ [supported patterns](supported-patterns.md#public-project-patterns-dev7-second-round).
149
+
150
+ ## Third round: the dev7 report (dev8)
151
+
152
+ The user ran dev7 on the private project and returned a second anonymized
153
+ report. Structure was fixed (0 errors; 58/58 files; 3,017 column versions, 388
154
+ frames and 23 sources instead of 11, 32 and 0; 2,053 of 2,300 diagnostics were
155
+ `info`), but `graph.select(<target>)` still failed, so the notebook cell
156
+ stopped before exporting the trace. The report narrowed the cause to the SQL
157
+ boundary:
158
+
159
+ | Report finding | Root cause in dev7 |
160
+ | --- | --- |
161
+ | `CT_PARTIAL_SQL ... ParseError` on an extractor | SQL reached through an unresolved `run_query` parameter was parsed with no dialect; BigQuery backticks do not parse in generic SQL |
162
+ | `CT_PARTIAL_SQL` on interpolations | Unknown interpolated fragments (a dynamic `AND ...` clause) made the statement unparsable |
163
+ | 63 `CT_INFERRED_FRAME` on `spark.sql(build_query()).toPandas()` | The conversion's own call has no SQL argument; the receiver's SQL text was not carried over |
164
+ | Target is "a source-table field consumed inside the SQL" | Physical columns were text in `sources[].fields[].inputs`, never lineage nodes, so no selection could find them |
165
+
166
+ The synthetic reproduction gained `quality_extract.py` (f-string SQL with
167
+ backticks, a helper fragment, an unknown metric column, an uncalled extractor)
168
+ and `topic_changes_extract.py` (`spark.sql(...).toPandas()` with an injected
169
+ session). On dev7 it reproduced every finding; on dev8:
170
+
171
+ | Check | dev7 | dev8 |
172
+ | --- | --- | --- |
173
+ | `select("contact_attempts")` (only inside `SUM(...)`) | not found | database column → `n_attempts` → `resolution_rate` |
174
+ | Template extractor SQL with backticks and a dynamic clause | no fields (comment-led text not recognized; backticks alone: ParseError) | 5 fields; `SUM({metric})` partial; recovery notes |
175
+ | `spark.sql(build_query(days)).toPandas()` | inferred frame, unknown columns | SQL source, 4 fields, complete schema |
176
+ | `select("n_attempts")` (called path and template) | ambiguous, 7 identities | ambiguous, 5 identities, called path ranked first |
177
+ | Warnings | 0 | 0 |
178
+
179
+ Changes, with ADR-059 to ADR-064:
180
+
181
+ - **Database columns are nodes** (schema 0.6, `dataframes[].kind == "table"`).
182
+ SQL fields read them as `pass`, `rename` or derivations; WHERE/JOIN/GROUP/ORDER
183
+ columns are `filter` consumptions only. One fully named relation is one
184
+ identity across extractions.
185
+ - **Runtime-assembled SQL.** Unknown interpolations are labeled with their
186
+ Python expression (`{table}`); `%` formatting, `str.format`, concatenation
187
+ with unknown strings and `join` over known generator expressions keep or fold
188
+ their text; SQL may start with comments.
189
+ - **Parsing recovery.** Dialect hints from the call shape, a project
190
+ `sql_dialect`, then fallback dialects; unplaceable fragments are dropped only
191
+ as a last resort. Every recovery is an `info` note, and the scan suggests the
192
+ dialect to declare.
193
+ - **Conversions keep receiver SQL**: `spark.sql(text).toPandas()`,
194
+ `sdf.toPandas()`, `client.query(text).to_dataframe()`; a Spark transformation
195
+ in between drops the query. A name whose `.query/.sql/.execute` receives SQL
196
+ text is a SQL client, not a DataFrame.
197
+ - **Selection.** `LineageGraph.candidates` ranks identities (called code before
198
+ templates, earliest origin first). A database column and its same-name SQL
199
+ output are one selectable family. The notebook cell continues with the first
200
+ candidate (`ON_AMBIGUOUS = "first"`) and lists the others; a missing name
201
+ that appears in unresolved SQL text is pointed out with its location.
202
+
203
+ The public-project harness was rerun on dev8: no crashes, every snapshot
204
+ round-trips, and frames, columns and warnings are unchanged in 11 of 13
205
+ projects (none of them reads SQL into Pandas through the shapes above). In owid,
206
+ `%` formatting now folds constant column names such as
207
+ `"days_since_%sth_case" % THRESHOLD`, so more code is modeled and some existing
208
+ gaps after it are now reported (+74 `CT_OPAQUE_CALL`); disabling the new text
209
+ rules reproduces the dev7 numbers exactly. owid analysis time is unchanged
210
+ (about 39 s). Hand-written precision checks: 14/14 expected, 0/9 forbidden.
211
+
212
+ ## Fourth round: the dev8 run and an extraction census (dev9)
213
+
214
+ The user ran dev8 on the private project. Extraction now worked (3,140 column
215
+ versions, 0 errors; physical table fields appeared among modeled names), but
216
+ the run used a pre-dev7 copy of the notebook cell, which printed every
217
+ informational note; the platform truncated the output, and the cell stopped at
218
+ "Column not found with these filters". The visible diagnostics after extraction
219
+ pointed at results kept in dicts and combined with `acc = None` accumulators
220
+ (`tot.merge(...)` on an unresolved receiver, `CT_UNRESOLVED_RECEIVER` runs,
221
+ `pd.concat` and `groupby` on lost frames).
222
+
223
+ Synthetic reproductions confirmed three defects: a dict filled by subscript was
224
+ still the empty literal, so loops over it were skipped silently; iterating
225
+ `frames.items()` invalidated the frames; and a loop inspected once never saw the
226
+ accumulator's `merge` branch. A fourth issue was selection: a database column
227
+ belonged only to the function of its first reading site (often the injected
228
+ `run_query` closure), so a `function=` filter on the extractor excluded it.
229
+ All four are fixed; the synthetic example now includes a dict of results
230
+ combined by an accumulator (`combine_sources.py`).
231
+
232
+ The user also asked for a map of extraction architectures. A static census of
233
+ public projects classified every extraction call shape and compared it with
234
+ ColTrace's output; the result is [extraction architectures](extraction-architectures.md).
235
+ Shapes fixed in this round: `SELECT *` open schemas, `UNION`, tables read by
236
+ name, `.result()` and row-only steps before conversion, literal projections,
237
+ keyword options on conversions, SQL files, and SQL clients whose name looked
238
+ like a DataFrame.
239
+
240
+ ## Remaining boundaries
241
+
242
+ This is still bounded abstract interpretation, not a general control-flow solver.
243
+ Loops over unknown collections are summarized by one possible iteration joined
244
+ with zero iterations. `groupby(...).apply`, `stack`/`unstack`, `crosstab` and
245
+ row-wise callbacks with side effects remain unmodeled results. Calls to unknown
246
+ helpers that receive a DataFrame still interrupt its provenance unless declared
247
+ as `observers`. Merge suffixes and
248
+ row multiplicity are not resolved. Async functions, reflection, `global`/`nonlocal`
249
+ writes and properties remain unresolved. `.ipynb` JSON files, live cells and
250
+ `dbutils.notebook.run` (a separate namespace) are not inputs. Opaque calls that
251
+ receive a DataFrame still interrupt its provenance.
252
+
253
+ The next acceptance input is a rerun of the internal compatibility report on
254
+ dev7, identifying the first remaining identity break for the target column.
@@ -63,6 +63,19 @@ choices made within the user's authorization.
63
63
  | ADR-056 | User-declared observers | `[tool.coltrace].observers` lets users assert that project helpers only read their arguments, like source contracts for extraction |
64
64
  | ADR-057 | Evidence strength for inferred frames | DataFrame-only use promotes any unresolved result; weaker use (string subscripts, shared names) only supports documented conversions; Spark-only use rules frames out |
65
65
  | ADR-058 | Model reshaping with open derived schemas | Labels created from data derive from identified input columns instead of being unmodeled |
66
+ | ADR-059 | Database columns are lineage nodes (schema 0.6) | The report's target column existed only inside SQL; `table` frames make physical columns selectable while SQL controls stay row controls |
67
+ | ADR-060 | One identity per fully named relation | The same table read by several extractions is one node; relations named by unknown interpolations stay local to their site |
68
+ | ADR-061 | Dialect fallback with explicit notes | Text that does not parse in its known or configured dialect is retried in common dialects; success is reported, never silent |
69
+ | ADR-062 | Labeled placeholders and last-resort fragment removal | Unknown interpolations are shown as their Python expression; unparsable dynamic fragments are dropped only after every dialect fails, and the schema becomes incomplete |
70
+ | ADR-063 | Documented conversions keep their receiver's SQL | `spark.sql(text).toPandas()` describes the SELECT rows; a transformation in between drops the query |
71
+ | ADR-064 | Ranked, never merged, ambiguous candidates | Called code before templates and the earliest origin first; a database column and its same-name SQL output form one selectable family |
72
+ | ADR-065 | Track dict entries; unroll known-length collections | Results kept in dicts were lost silently; exact iteration keeps each frame's identity |
73
+ | ADR-066 | Inspect a second loop iteration for `None` accumulators | `acc = None` patterns need the state one iteration produces; bounded to loops that carry a `None` |
74
+ | ADR-067 | Filters match database columns along the call chain | A table column is used by every extractor that reads it, not only by the closure where SQL runs |
75
+ | ADR-068 | `SELECT *` is an open table schema | Used columns become table columns; lookups through merges of different frames never add columns to a table |
76
+ | ADR-069 | Read SQL files as data | Only inside the analyzed root, SQL/text suffixes, bounded size; never executed |
77
+ | ADR-070 | Keep ADR-057 for conversions | A census suggested promoting every `toPandas()` after SQL; precision tests and ADR-057 keep usage, annotations or contracts as the evidence |
78
+ | ADR-071 | One record per distinct diagnostic | Unrolled iterations and repeated calling contexts produced identical records (the private run showed one line repeated dozens of times); identical records carry no extra information |
66
79
 
67
80
  The initial codename was FeatureTrace. The early architecture proposed reusable
68
81
  function summaries; direct contextual interpretation is used first because it