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.
- {coltrace-0.1.0.dev7/src/coltrace.egg-info → coltrace-0.1.0.dev9}/PKG-INFO +15 -7
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/README.md +14 -6
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/backlog.md +13 -1
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/compatibility-checkpoint.md +254 -159
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/decisions.md +13 -0
- coltrace-0.1.0.dev9/docs/extraction-architectures.md +66 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/lineage-json.md +25 -4
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/supported-patterns.md +113 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/troubleshooting.md +31 -1
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/README.md +39 -33
- coltrace-0.1.0.dev9/examples/databricks_project/pipeline_a/extract/quality_extract.py +43 -0
- coltrace-0.1.0.dev9/examples/databricks_project/pipeline_a/main_orchestrator.py +44 -0
- coltrace-0.1.0.dev9/examples/databricks_project/pipeline_a/transform/combine_sources.py +9 -0
- coltrace-0.1.0.dev9/examples/databricks_project/shared/topic_changes_extract.py +30 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/notebook_cell.py +33 -6
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/pyproject.toml +54 -54
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/__init__.py +22 -22
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/analysis.py +1036 -58
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/cli.py +7 -1
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/compatibility.py +77 -18
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/exporters.py +717 -711
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/graph.py +500 -386
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/journey.py +18 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/lineage_models.py +122 -115
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/models.py +92 -92
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/scanner.py +20 -2
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/source_readers.py +85 -83
- coltrace-0.1.0.dev9/src/coltrace/sql_sources.py +669 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9/src/coltrace.egg-info}/PKG-INFO +15 -7
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/SOURCES.txt +7 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_database_sources.py +296 -287
- coltrace-0.1.0.dev9/tests/integration/test_databricks_example.py +321 -0
- coltrace-0.1.0.dev9/tests/integration/test_extraction_architectures.py +277 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_notebook_reporting.py +42 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_project_compatibility.py +458 -458
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_public_project_patterns.py +7 -2
- coltrace-0.1.0.dev9/tests/integration/test_result_containers.py +222 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_sql_reader_boundaries.py +321 -300
- coltrace-0.1.0.dev9/tests/integration/test_sql_templates.py +310 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_exporters.py +286 -286
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_source_models.py +283 -283
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_source_presentation.py +118 -116
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_sql_sources.py +453 -350
- coltrace-0.1.0.dev7/examples/databricks_project/pipeline_a/main_orchestrator.py +0 -25
- coltrace-0.1.0.dev7/src/coltrace/sql_sources.py +0 -355
- coltrace-0.1.0.dev7/tests/integration/test_databricks_example.py +0 -131
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/LICENSE +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/MANIFEST.in +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/accuracy.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/architecture.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/column-history.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/column-journey-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/database-patterns.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/database-sources.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/discovery-json.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/graph-workspace-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/history-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/mvp-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/nlp-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/other-column-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/pandas-sources-proposal.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/phase-1.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/read-capture.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/docs/source-checkpoint.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/README.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/build_query.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/database_pipeline/pyproject.toml +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/main_orchestrator.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/__init__.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/extract/__init__.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/extract/primary_extract.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/publish/__init__.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/publish/serialize_webapp.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/transform/__init__.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/pipeline_a/transform/priority_rank.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/__init__.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/config.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/token_tracker.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/databricks_project/shared/warehouse.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/extract/prices.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/features/pricing.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/pricing_pipeline/transform/pricing.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/README.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/project/input.csv +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/project/pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/read_capture/run_capture.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/README.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/extract.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/robustness_pipeline/transform.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/sample_pipeline/prices.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/README.md +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/processing.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/readers.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/examples/text_pipeline/text.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/setup.cfg +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/__main__.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/callbacks.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/capture.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/collector.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/history.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/nosql_sources.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/notebooks.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/pandas_api.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/py.typed +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/source_models.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/templates/graph.html +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace/visual_layout.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/dependency_links.txt +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/entry_points.txt +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/requires.txt +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/src/coltrace.egg-info/top_level.txt +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/extract/prices.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/features/pricing.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/fixtures/sample_pipeline/transform/pricing.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_annotation_effects.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_callback_roles.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_capture_workflow.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_cli.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_column_story.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_compatibility_cli.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_conditional_flow.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_journey_boundaries.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_lineage_acceptance.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_nosql_extraction.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_other_column_stories.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_pandas_boundaries.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_pandas_observation.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_production_patterns.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_reader_inventory.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_resolution_boundaries.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_scan_project.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_source_config.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_source_precision.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/integration/test_text_pipeline.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_analysis.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_callbacks.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_collector.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_compatibility.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_graph.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_graph_workspace.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_history.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_journey.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_nosql_sources.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_notebooks.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_read_capture.py +0 -0
- {coltrace-0.1.0.dev7 → coltrace-0.1.0.dev9}/tests/unit/test_scanner.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
62
|
-
|
|
63
|
-
`
|
|
64
|
-
|
|
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.
|
|
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.
|
|
36
|
-
|
|
37
|
-
`
|
|
38
|
-
|
|
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
|
|
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.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
python -m
|
|
102
|
-
python -m
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
`
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|