sql-glider 0.1.26__tar.gz → 0.1.27__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 (166) hide show
  1. {sql_glider-0.1.26 → sql_glider-0.1.27}/PKG-INFO +2 -1
  2. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/docs/graph-lineage.md +14 -0
  3. {sql_glider-0.1.26 → sql_glider-0.1.27}/pyproject.toml +1 -1
  4. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/_version.py +2 -2
  5. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/diagram_formatters.py +88 -1
  6. {sql_glider-0.1.26 → sql_glider-0.1.27}/uv.lock +8 -0
  7. sql_glider-0.1.26/graph.json +0 -5319
  8. sql_glider-0.1.26/lineage.json +0 -0
  9. sql_glider-0.1.26/mm.md +0 -0
  10. {sql_glider-0.1.26 → sql_glider-0.1.27}/.github/workflows/ci.yml +0 -0
  11. {sql_glider-0.1.26 → sql_glider-0.1.27}/.github/workflows/docs.yml +0 -0
  12. {sql_glider-0.1.26 → sql_glider-0.1.27}/.github/workflows/publish.yml +0 -0
  13. {sql_glider-0.1.26 → sql_glider-0.1.27}/.gitignore +0 -0
  14. {sql_glider-0.1.26 → sql_glider-0.1.27}/.python-version +0 -0
  15. {sql_glider-0.1.26 → sql_glider-0.1.27}/ARCHITECTURE.md +0 -0
  16. {sql_glider-0.1.26 → sql_glider-0.1.27}/CLAUDE.md +0 -0
  17. {sql_glider-0.1.26 → sql_glider-0.1.27}/LICENSE +0 -0
  18. {sql_glider-0.1.26 → sql_glider-0.1.27}/README.md +0 -0
  19. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/.github/workflows/docs.yml +0 -0
  20. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/docs/catalogs.md +0 -0
  21. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/docs/index.md +0 -0
  22. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/docs/static/plotly-dash-example.png +0 -0
  23. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/docs/static/sqlglider-logo-transparent.png +0 -0
  24. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/docs/templating.md +0 -0
  25. {sql_glider-0.1.26 → sql_glider-0.1.27}/docs/zensical.toml +0 -0
  26. {sql_glider-0.1.26 → sql_glider-0.1.27}/examples/plotly_viewer.py +0 -0
  27. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-05-column-level-lineage.md +0 -0
  28. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-05-reverse-lineage.md +0 -0
  29. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-06-config-file-support.md +0 -0
  30. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-06-graph-lineage.md +0 -0
  31. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-06-unify-single-multi-query.md +0 -0
  32. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-07-sample-data-model.md +0 -0
  33. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-07-sql-templating.md +0 -0
  34. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-08-tables-command.md +0 -0
  35. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-09-graph-query-paths.md +0 -0
  36. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-13-dissect-command.md +0 -0
  37. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2025-12-14-tables-pull-command.md +0 -0
  38. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-25-fix-union-lineage-chain.md +0 -0
  39. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-26-file-scoped-schema-context.md +0 -0
  40. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-28-sparksql-table-extraction.md +0 -0
  41. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-29-no-star-flag.md +0 -0
  42. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-29-resolve-schema.md +0 -0
  43. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-29-schema-pruning-optimization.md +0 -0
  44. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-01-29-tables-scrape-command.md +0 -0
  45. {sql_glider-0.1.26 → sql_glider-0.1.27}/plans/2026-02-02-diagram-output-formats.md +0 -0
  46. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/README.md +0 -0
  47. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/business/expire_dim_customer.sql +0 -0
  48. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/business/load_fact_orders.sql +0 -0
  49. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/business/load_fact_payments.sql +0 -0
  50. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/business/merge_dim_customer.sql +0 -0
  51. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/business/merge_dim_product.sql +0 -0
  52. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/business/update_dim_customer_metrics.sql +0 -0
  53. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/complex/conditional_merge.sql +0 -0
  54. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/complex/cte_insert.sql +0 -0
  55. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/complex/multi_table_transform.sql +0 -0
  56. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/dim_customer.sql +0 -0
  57. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/dim_product.sql +0 -0
  58. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/fact_orders.sql +0 -0
  59. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/fact_payments.sql +0 -0
  60. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/raw_addresses.sql +0 -0
  61. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/raw_customers.sql +0 -0
  62. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/raw_order_items.sql +0 -0
  63. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/raw_orders.sql +0 -0
  64. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/raw_payments.sql +0 -0
  65. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/raw_products.sql +0 -0
  66. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/stg_customers.sql +0 -0
  67. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/stg_orders.sql +0 -0
  68. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/stg_payments.sql +0 -0
  69. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/ddl/stg_products.sql +0 -0
  70. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/incremental/incr_fact_orders.sql +0 -0
  71. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/incremental/incr_fact_payments.sql +0 -0
  72. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/incremental/incr_pres_sales_summary.sql +0 -0
  73. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/maintenance/delete_expired_customers.sql +0 -0
  74. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/maintenance/update_product_status.sql +0 -0
  75. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/presentation/load_pres_customer_360.sql +0 -0
  76. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/presentation/load_pres_customer_cohort.sql +0 -0
  77. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/presentation/load_pres_product_performance.sql +0 -0
  78. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/presentation/load_pres_sales_summary.sql +0 -0
  79. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/staging/load_stg_customers.sql +0 -0
  80. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/staging/load_stg_orders.sql +0 -0
  81. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/staging/load_stg_payments.sql +0 -0
  82. {sql_glider-0.1.26 → sql_glider-0.1.27}/sample_data_model/staging/load_stg_products.sql +0 -0
  83. {sql_glider-0.1.26 → sql_glider-0.1.27}/sqlglider.toml.example +0 -0
  84. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/__init__.py +0 -0
  85. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/catalog/__init__.py +0 -0
  86. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/catalog/base.py +0 -0
  87. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/catalog/databricks.py +0 -0
  88. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/catalog/registry.py +0 -0
  89. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/cli.py +0 -0
  90. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/dissection/__init__.py +0 -0
  91. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/dissection/analyzer.py +0 -0
  92. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/dissection/formatters.py +0 -0
  93. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/dissection/models.py +0 -0
  94. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/global_models.py +0 -0
  95. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/__init__.py +0 -0
  96. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/builder.py +0 -0
  97. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/formatters.py +0 -0
  98. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/merge.py +0 -0
  99. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/models.py +0 -0
  100. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/query.py +0 -0
  101. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/graph/serialization.py +0 -0
  102. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/lineage/__init__.py +0 -0
  103. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/lineage/analyzer.py +0 -0
  104. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/lineage/formatters.py +0 -0
  105. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/schema/__init__.py +0 -0
  106. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/schema/extractor.py +0 -0
  107. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/templating/__init__.py +0 -0
  108. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/templating/base.py +0 -0
  109. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/templating/jinja.py +0 -0
  110. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/templating/registry.py +0 -0
  111. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/templating/variables.py +0 -0
  112. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/utils/__init__.py +0 -0
  113. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/utils/config.py +0 -0
  114. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/utils/file_utils.py +0 -0
  115. {sql_glider-0.1.26 → sql_glider-0.1.27}/src/sqlglider/utils/schema.py +0 -0
  116. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/__init__.py +0 -0
  117. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/multi_file_queries/analytics_pipeline.sql +0 -0
  118. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/multi_file_queries/analytics_pipeline_union_merge.sql +0 -0
  119. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/multi_file_queries/customers.sql +0 -0
  120. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/multi_file_queries/orders.sql +0 -0
  121. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/multi_file_queries/reports.sql +0 -0
  122. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/multi_file_queries/view_based_merge.sql +0 -0
  123. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_cte.sql +0 -0
  124. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_cte_query.sql +0 -0
  125. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_cte_view_star.sql +0 -0
  126. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_generated_column_query.sql +0 -0
  127. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_multi.sql +0 -0
  128. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_multi_query.sql +0 -0
  129. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_single_query.sql +0 -0
  130. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_subquery.sql +0 -0
  131. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_tables.sql +0 -0
  132. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_view.sql +0 -0
  133. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/original_queries/test_view_window_cte.sql +0 -0
  134. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/fixtures/sample_manifest.csv +0 -0
  135. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/__init__.py +0 -0
  136. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/catalog/__init__.py +0 -0
  137. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/catalog/test_base.py +0 -0
  138. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/catalog/test_databricks.py +0 -0
  139. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/catalog/test_registry.py +0 -0
  140. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/dissection/__init__.py +0 -0
  141. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/dissection/test_analyzer.py +0 -0
  142. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/dissection/test_formatters.py +0 -0
  143. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/dissection/test_models.py +0 -0
  144. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/__init__.py +0 -0
  145. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_builder.py +0 -0
  146. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_diagram_formatters.py +0 -0
  147. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_formatters.py +0 -0
  148. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_merge.py +0 -0
  149. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_models.py +0 -0
  150. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_query.py +0 -0
  151. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/graph/test_serialization.py +0 -0
  152. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/lineage/__init__.py +0 -0
  153. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/lineage/test_analyzer.py +0 -0
  154. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/lineage/test_formatters.py +0 -0
  155. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/schema/__init__.py +0 -0
  156. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/schema/test_extractor.py +0 -0
  157. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/templating/__init__.py +0 -0
  158. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/templating/test_base.py +0 -0
  159. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/templating/test_jinja.py +0 -0
  160. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/templating/test_registry.py +0 -0
  161. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/templating/test_variables.py +0 -0
  162. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/test_cli.py +0 -0
  163. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/utils/__init__.py +0 -0
  164. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/utils/test_config.py +0 -0
  165. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/utils/test_file_utils.py +0 -0
  166. {sql_glider-0.1.26 → sql_glider-0.1.27}/tests/sqlglider/utils/test_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-glider
3
- Version: 0.1.26
3
+ Version: 0.1.27
4
4
  Summary: SQL Utility Toolkit for better understanding, use, and governance of your queries in a native environment.
5
5
  Project-URL: Homepage, https://github.com/rycowhi/sql-glider/
6
6
  Project-URL: Repository, https://github.com/rycowhi/sql-glider/
@@ -30,6 +30,7 @@ Provides-Extra: databricks
30
30
  Requires-Dist: databricks-sdk>=0.20.0; extra == 'databricks'
31
31
  Provides-Extra: plotly
32
32
  Requires-Dist: plotly>=5.0.0; extra == 'plotly'
33
+ Requires-Dist: pygraphviz>=1.11; extra == 'plotly'
33
34
  Description-Content-Type: text/markdown
34
35
 
35
36
  # SQL Glider
@@ -329,6 +329,20 @@ The `plotly` format outputs a JSON figure specification that can be loaded into
329
329
  pip install sql-glider[plotly]
330
330
  ```
331
331
 
332
+ !!! tip "Better Layout with Graphviz"
333
+
334
+ For complex graphs with many nodes and edges, installing [Graphviz](https://graphviz.org/download/) on your system enables an optimized layout algorithm that minimizes edge crossings. Without Graphviz, a simpler layout is used that may result in overlapping edges.
335
+
336
+ ```bash
337
+ # Install Graphviz first (system package)
338
+ # Windows: choco install graphviz
339
+ # macOS: brew install graphviz
340
+ # Linux: apt install graphviz graphviz-dev
341
+
342
+ # Then install sql-glider with plotly extra
343
+ pip install sql-glider[plotly]
344
+ ```
345
+
332
346
  **Example usage with Dash:**
333
347
 
334
348
  ```python
@@ -35,7 +35,7 @@ dependencies = [
35
35
 
36
36
  [project.optional-dependencies]
37
37
  databricks = ["databricks-sdk>=0.20.0"]
38
- plotly = ["plotly>=5.0.0"]
38
+ plotly = ["plotly>=5.0.0", "pygraphviz>=1.11"]
39
39
 
40
40
  [project.urls]
41
41
  Homepage = "https://github.com/rycowhi/sql-glider/"
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.26'
32
- __version_tuple__ = version_tuple = (0, 1, 26)
31
+ __version__ = version = '0.1.27'
32
+ __version_tuple__ = version_tuple = (0, 1, 27)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -364,7 +364,11 @@ def _compute_layered_layout(
364
364
  x_spacing: float = 250.0,
365
365
  y_spacing: float = 100.0,
366
366
  ) -> dict[str, tuple[float, float]]:
367
- """Compute layered layout positions for nodes using topological ordering.
367
+ """Compute layered layout positions for nodes using Graphviz's dot algorithm.
368
+
369
+ Uses Graphviz's dot layout engine (Sugiyama algorithm) which minimizes edge
370
+ crossings by using the barycenter heuristic to optimize node ordering within
371
+ each layer.
368
372
 
369
373
  Positions nodes in layers from left to right based on their dependencies.
370
374
  Nodes with no incoming edges are placed in layer 0, their dependents in
@@ -382,6 +386,89 @@ def _compute_layered_layout(
382
386
  if not nodes:
383
387
  return {}
384
388
 
389
+ try:
390
+ import pygraphviz # noqa: F401 # type: ignore[import-not-found]
391
+
392
+ return _compute_graphviz_layout(nodes, edges, x_spacing, y_spacing)
393
+ except ImportError:
394
+ # Fallback to simple layered layout if pygraphviz not available
395
+ return _compute_simple_layered_layout(nodes, edges, x_spacing, y_spacing)
396
+
397
+
398
+ def _compute_graphviz_layout(
399
+ nodes: list[str],
400
+ edges: list[tuple[str, str]],
401
+ x_spacing: float = 250.0,
402
+ y_spacing: float = 100.0,
403
+ ) -> dict[str, tuple[float, float]]:
404
+ """Compute layout using Graphviz's dot algorithm with crossing minimization.
405
+
406
+ Args:
407
+ nodes: List of node identifiers
408
+ edges: List of (source, target) edge tuples
409
+ x_spacing: Horizontal spacing between layers
410
+ y_spacing: Vertical spacing between nodes in the same layer
411
+
412
+ Returns:
413
+ Dictionary mapping node identifiers to (x, y) positions
414
+ """
415
+ import pygraphviz as pgv # type: ignore[import-not-found]
416
+
417
+ # Create directed graph
418
+ g = pgv.AGraph(directed=True, rankdir="LR")
419
+
420
+ # Set graph attributes for spacing
421
+ g.graph_attr["ranksep"] = str(x_spacing / 72.0) # Convert pixels to inches
422
+ g.graph_attr["nodesep"] = str(y_spacing / 72.0)
423
+
424
+ # Add nodes
425
+ for node in nodes:
426
+ g.add_node(node)
427
+
428
+ # Add edges (only for nodes that exist in our node list)
429
+ node_set = set(nodes)
430
+ for src, tgt in edges:
431
+ if src in node_set and tgt in node_set:
432
+ g.add_edge(src, tgt)
433
+
434
+ # Compute layout using dot algorithm
435
+ g.layout(prog="dot")
436
+
437
+ # Extract positions
438
+ positions: dict[str, tuple[float, float]] = {}
439
+ for node in nodes:
440
+ n = g.get_node(node)
441
+ # Position is returned as "x,y" string in points (1/72 inch)
442
+ pos_str = n.attr.get("pos", "0,0")
443
+ if pos_str:
444
+ x_str, y_str = pos_str.split(",")
445
+ # Convert from points to our coordinate system
446
+ x = float(x_str)
447
+ y = float(y_str)
448
+ positions[node] = (x, y)
449
+
450
+ return positions
451
+
452
+
453
+ def _compute_simple_layered_layout(
454
+ nodes: list[str],
455
+ edges: list[tuple[str, str]],
456
+ x_spacing: float = 250.0,
457
+ y_spacing: float = 100.0,
458
+ ) -> dict[str, tuple[float, float]]:
459
+ """Fallback simple layered layout using topological ordering.
460
+
461
+ Used when pygraphviz is not available. Does not minimize edge crossings.
462
+
463
+ Args:
464
+ nodes: List of node identifiers
465
+ edges: List of (source, target) edge tuples
466
+ x_spacing: Horizontal spacing between layers
467
+ y_spacing: Vertical spacing between nodes in the same layer
468
+
469
+ Returns:
470
+ Dictionary mapping node identifiers to (x, y) positions
471
+ """
385
472
  # Build adjacency structures
386
473
  incoming: dict[str, set[str]] = defaultdict(set)
387
474
  outgoing: dict[str, set[str]] = defaultdict(set)
@@ -814,6 +814,12 @@ wheels = [
814
814
  { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
815
815
  ]
816
816
 
817
+ [[package]]
818
+ name = "pygraphviz"
819
+ version = "1.14"
820
+ source = { registry = "https://pypi.org/simple/" }
821
+ sdist = { url = "https://files.pythonhosted.org/packages/66/ca/823d5c74a73d6b8b08e1f5aea12468ef334f0732c65cbb18df2a7f285c87/pygraphviz-1.14.tar.gz", hash = "sha256:c10df02377f4e39b00ae17c862f4ee7e5767317f1c6b2dfd04cea6acc7fc2bea", size = 106003, upload-time = "2024-09-29T18:31:12.471Z" }
822
+
817
823
  [[package]]
818
824
  name = "pymdown-extensions"
819
825
  version = "10.20.1"
@@ -1161,6 +1167,7 @@ databricks = [
1161
1167
  ]
1162
1168
  plotly = [
1163
1169
  { name = "plotly" },
1170
+ { name = "pygraphviz" },
1164
1171
  ]
1165
1172
 
1166
1173
  [package.dev-dependencies]
@@ -1185,6 +1192,7 @@ requires-dist = [
1185
1192
  { name = "jinja2", specifier = ">=3.0.0" },
1186
1193
  { name = "plotly", marker = "extra == 'plotly'", specifier = ">=5.0.0" },
1187
1194
  { name = "pydantic", specifier = ">=2.0.0" },
1195
+ { name = "pygraphviz", marker = "extra == 'plotly'", specifier = ">=1.11" },
1188
1196
  { name = "rich", specifier = ">=13.0.0" },
1189
1197
  { name = "rustworkx", specifier = ">=0.15.0" },
1190
1198
  { name = "sqlglot", extras = ["rs"], specifier = ">=25.0.0" },