quickdbd-cli 0.5.0__tar.gz → 0.6.1__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 (269) hide show
  1. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/NOTICE +1 -0
  2. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/PKG-INFO +7 -6
  3. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/README.md +5 -5
  4. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/pyproject.toml +2 -1
  5. quickdbd_cli-0.6.1/src/quickdbd_cli/__init__.py +1 -0
  6. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/style.css +2 -2
  7. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/syntax-reference.md +2 -1
  8. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/viewer.js +1 -1
  9. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/cli.py +5 -5
  10. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/convert.py +14 -3
  11. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/dbml.py +23 -3
  12. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/dbt.py +5 -3
  13. quickdbd_cli-0.6.1/src/quickdbd_cli/dbtyml.py +275 -0
  14. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/diff.py +1 -1
  15. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/measure.py +10 -8
  16. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/model.py +36 -0
  17. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/parser.py +51 -7
  18. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/serve.py +20 -9
  19. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/tags.py +2 -2
  20. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/dbt_project.yml +4 -0
  21. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/erd/schema.txt +34 -0
  22. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/models/marts/customers.sql +1 -0
  23. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/models/marts/docs.md +7 -0
  24. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/models/marts/orders.sql +1 -0
  25. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/models/marts/payments.sql +1 -0
  26. quickdbd_cli-0.6.1/tests/fixtures/dbt-yml/models/marts/schema.yml +36 -0
  27. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_convert.py +7 -3
  28. quickdbd_cli-0.6.1/tests/test_dbtyml.py +229 -0
  29. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_gallery.py +19 -0
  30. quickdbd_cli-0.6.1/tests/test_gallery_hook.py +30 -0
  31. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_home_hook.py +20 -1
  32. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_viewer_contract.py +14 -4
  33. quickdbd_cli-0.5.0/src/quickdbd_cli/__init__.py +0 -1
  34. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/.gitignore +0 -0
  35. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/LICENSE +0 -0
  36. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/quickdbd-sample.txt +0 -0
  37. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/synthetic/finance.txt +0 -0
  38. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/synthetic/hr.txt +0 -0
  39. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/synthetic/inventory.txt +0 -0
  40. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/synthetic/marketing.txt +0 -0
  41. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/synthetic/qdbd.layout.json +0 -0
  42. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/examples/synthetic/sales.txt +0 -0
  43. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/scripts/build_gallery.py +0 -0
  44. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/scripts/gen_font_metrics.py +0 -0
  45. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/scripts/gen_schema.py +0 -0
  46. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/scripts/gen_thumbnails.py +0 -0
  47. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/scripts/import_dbml_corpus.py +0 -0
  48. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/scripts/release.sh +0 -0
  49. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/__main__.py +0 -0
  50. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/agent.py +0 -0
  51. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
  52. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
  53. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
  54. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
  55. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
  56. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/editor.js +0 -0
  57. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
  58. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
  59. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/font-metrics.json +0 -0
  60. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/icon.svg +0 -0
  61. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/assets/render.js +0 -0
  62. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/export.py +0 -0
  63. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/html.py +0 -0
  64. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/jsrt.py +0 -0
  65. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/layout.py +0 -0
  66. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/mcp.py +0 -0
  67. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/project.py +0 -0
  68. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/share.py +0 -0
  69. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/src/quickdbd_cli/targets.py +0 -0
  70. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml/team.dbml +0 -0
  71. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/LICENSE +0 -0
  72. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/README.md +0 -0
  73. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/alias_of_duplicated_names.dbml +0 -0
  74. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/alias_of_duplicated_names.json +0 -0
  75. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/dep.dbml +0 -0
  76. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/dep.json +0 -0
  77. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_columns.dbml +0 -0
  78. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_columns.json +0 -0
  79. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_enum_field.dbml +0 -0
  80. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_enum_field.json +0 -0
  81. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_name.dbml +0 -0
  82. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_name.json +0 -0
  83. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_names.dbml +0 -0
  84. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_names.json +0 -0
  85. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_table_partial_injections.dbml +0 -0
  86. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/duplicate_table_partial_injections.json +0 -0
  87. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/enum_as_default_column_value.dbml +0 -0
  88. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/enum_as_default_column_value.json +0 -0
  89. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/enum_name.dbml +0 -0
  90. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/enum_name.json +0 -0
  91. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/erroneous.dbml +0 -0
  92. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/erroneous.json +0 -0
  93. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/multiple_notes_in_table_group.dbml +0 -0
  94. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/multiple_notes_in_table_group.json +0 -0
  95. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index.dbml +0 -0
  96. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index.json +0 -0
  97. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index_table_partial.dbml +0 -0
  98. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_column_in_index_table_partial.json +0 -0
  99. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table.dbml +0 -0
  100. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table.json +0 -0
  101. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table_partial.dbml +0 -0
  102. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/nonexisting_inline_ref_column_in_table_partial.json +0 -0
  103. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/ref_validator.dbml +0 -0
  104. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/ref_validator.json +0 -0
  105. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/sticky_notes_validator.dbml +0 -0
  106. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/sticky_notes_validator.json +0 -0
  107. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/table_partial.dbml +0 -0
  108. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/table_partial.json +0 -0
  109. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/unknown_table_group_field.dbml +0 -0
  110. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/binder/unknown_table_group_field.json +0 -0
  111. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/array_type.dbml +0 -0
  112. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/array_type.json +0 -0
  113. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/checks.dbml +0 -0
  114. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/checks.json +0 -0
  115. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/circular_ref_1_inline_1_element.dbml +0 -0
  116. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/circular_ref_1_inline_1_element.json +0 -0
  117. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_elements.dbml +0 -0
  118. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_elements.json +0 -0
  119. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_inlines.dbml +0 -0
  120. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/circular_ref_2_inlines.json +0 -0
  121. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/column_caller_type.dbml +0 -0
  122. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/column_caller_type.json +0 -0
  123. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/comment.dbml +0 -0
  124. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/comment.json +0 -0
  125. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/composite_pk.dbml +0 -0
  126. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/composite_pk.json +0 -0
  127. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/default_tables.dbml +0 -0
  128. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/default_tables.json +0 -0
  129. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep.dbml +0 -0
  130. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep.json +0 -0
  131. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep_color.dbml +0 -0
  132. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep_color.json +0 -0
  133. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep_duplicate.dbml +0 -0
  134. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep_duplicate.json +0 -0
  135. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep_pipeline.dbml +0 -0
  136. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/dep_pipeline.json +0 -0
  137. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/enum_as_default_column_value.dbml +0 -0
  138. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/enum_as_default_column_value.json +0 -0
  139. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/enum_tables.dbml +0 -0
  140. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/enum_tables.json +0 -0
  141. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/erroneous.dbml +0 -0
  142. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/erroneous.json +0 -0
  143. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/general_schema.dbml +0 -0
  144. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/general_schema.json +0 -0
  145. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/header_color_tables.dbml +0 -0
  146. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/header_color_tables.json +0 -0
  147. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/index_table_partial.dbml +0 -0
  148. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/index_table_partial.json +0 -0
  149. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/index_tables.dbml +0 -0
  150. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/index_tables.json +0 -0
  151. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/metadata.dbml +0 -0
  152. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/metadata.json +0 -0
  153. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/metadata_errors.dbml +0 -0
  154. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/metadata_errors.json +0 -0
  155. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/multi_notes.dbml +0 -0
  156. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/multi_notes.json +0 -0
  157. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/multiline_string.dbml +0 -0
  158. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/multiline_string.json +0 -0
  159. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/negative_number.dbml +0 -0
  160. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/negative_number.json +0 -0
  161. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/note_normalize.dbml +0 -0
  162. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/note_normalize.json +0 -0
  163. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/note_normalize_with_top_empty_lines.dbml +0 -0
  164. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/note_normalize_with_top_empty_lines.json +0 -0
  165. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/old_undocumented_syntax.dbml +0 -0
  166. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/old_undocumented_syntax.json +0 -0
  167. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/primary_key.dbml +0 -0
  168. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/primary_key.json +0 -0
  169. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/project.dbml +0 -0
  170. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/project.json +0 -0
  171. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/ref_name_and_color_setting.dbml +0 -0
  172. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/ref_name_and_color_setting.json +0 -0
  173. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/ref_settings.dbml +0 -0
  174. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/ref_settings.json +0 -0
  175. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/referential_actions.dbml +0 -0
  176. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/referential_actions.json +0 -0
  177. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/self_referential_ref_in_tablepartial.dbml +0 -0
  178. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/self_referential_ref_in_tablepartial.json +0 -0
  179. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/sticky_notes.dbml +0 -0
  180. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/sticky_notes.json +0 -0
  181. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_element.dbml +0 -0
  182. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_element.json +0 -0
  183. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_group.dbml +0 -0
  184. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_group.json +0 -0
  185. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_group_element.dbml +0 -0
  186. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_group_element.json +0 -0
  187. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_group_settings.dbml +0 -0
  188. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_group_settings.json +0 -0
  189. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_partial.dbml +0 -0
  190. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_partial.json +0 -0
  191. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_reappear_tablegroup.dbml +0 -0
  192. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_reappear_tablegroup.json +0 -0
  193. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_settings.dbml +0 -0
  194. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/table_settings.json +0 -0
  195. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/tablepartial_causing_circular_ref.dbml +0 -0
  196. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/tablepartial_causing_circular_ref.json +0 -0
  197. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/two_inline_refs.dbml +0 -0
  198. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/interpreter/two_inline_refs.json +0 -0
  199. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/call_expression.dbml +0 -0
  200. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/call_expression.json +0 -0
  201. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/element_in_simple_body.dbml +0 -0
  202. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/element_in_simple_body.json +0 -0
  203. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/erroneous_setting.dbml +0 -0
  204. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/erroneous_setting.json +0 -0
  205. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/expression.dbml +0 -0
  206. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/expression.json +0 -0
  207. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/last_invalid_number.dbml +0 -0
  208. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/last_invalid_number.json +0 -0
  209. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/list_expression.dbml +0 -0
  210. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/list_expression.json +0 -0
  211. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/trailing_comments.dbml +0 -0
  212. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/trailing_comments.json +0 -0
  213. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/tuple_expression.dbml +0 -0
  214. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/parser/tuple_expression.json +0 -0
  215. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/checks.dbml +0 -0
  216. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/checks.json +0 -0
  217. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/complex_names.dbml +0 -0
  218. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/complex_names.json +0 -0
  219. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/dep.dbml +0 -0
  220. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/dep.json +0 -0
  221. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/enum.dbml +0 -0
  222. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/enum.json +0 -0
  223. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/invalid_args.dbml +0 -0
  224. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/invalid_args.json +0 -0
  225. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/public_schema.dbml +0 -0
  226. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/public_schema.json +0 -0
  227. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/redefined_note.dbml +0 -0
  228. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/redefined_note.json +0 -0
  229. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/ref_error_setting.dbml +0 -0
  230. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/ref_error_setting.json +0 -0
  231. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/ref_in_table.dbml +0 -0
  232. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/ref_in_table.json +0 -0
  233. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/schema_nested_tablegroup.dbml +0 -0
  234. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/schema_nested_tablegroup.json +0 -0
  235. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_group_settings.dbml +0 -0
  236. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_group_settings.json +0 -0
  237. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_partial_check.dbml +0 -0
  238. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_partial_check.json +0 -0
  239. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_partial_settings_general.dbml +0 -0
  240. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_partial_settings_general.json +0 -0
  241. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_settings_check.dbml +0 -0
  242. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_settings_check.json +0 -0
  243. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_settings_general.dbml +0 -0
  244. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/table_settings_general.json +0 -0
  245. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/wrong_sub_element_declarations.dbml +0 -0
  246. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/wrong_sub_element_declarations.json +0 -0
  247. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/wrong_table_partial_injection_syntax.dbml +0 -0
  248. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/dbml-ref/validator/wrong_table_partial_injection_syntax.json +0 -0
  249. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/jaffle/LICENSE +0 -0
  250. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/jaffle/README.md +0 -0
  251. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/jaffle/catalog.json +0 -0
  252. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/jaffle/manifest.json +0 -0
  253. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/fixtures/targets/cases.json +0 -0
  254. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_agent.py +0 -0
  255. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_dbml.py +0 -0
  256. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_dbml_pipeline.py +0 -0
  257. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_dbt.py +0 -0
  258. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_diff.py +0 -0
  259. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_mcp.py +0 -0
  260. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_packaging.py +0 -0
  261. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_parser.py +0 -0
  262. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_path.py +0 -0
  263. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_pipeline.py +0 -0
  264. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_render.py +0 -0
  265. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_select.py +0 -0
  266. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_serve.py +0 -0
  267. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_share.py +0 -0
  268. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_tags.py +0 -0
  269. {quickdbd_cli-0.5.0 → quickdbd_cli-0.6.1}/tests/test_targets.py +0 -0
@@ -25,6 +25,7 @@ The demo gallery (gallery/, not part of the Python package) includes schemas der
25
25
  Luis Rocha. MIT License.
26
26
  * dbt-synthea (gallery/synthea), OHDSI. Apache License 2.0. Trimmed dbt manifest and catalog only.
27
27
  * dbt_shopify (gallery/shopify), Fivetran. Apache License 2.0. Trimmed dbt manifest and catalog only.
28
+ * qdbd-dbt-demo (gallery/skytrax), Minh (Mark) Pham. Apache License 2.0. Hand-written ERD and dbt yml only.
28
29
 
29
30
  Each folder keeps the upstream licence in its LICENSE file.
30
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quickdbd-cli
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
5
5
  Project-URL: Homepage, https://qdbd.dev/
6
6
  Project-URL: Documentation, https://qdbd.dev/guide/
@@ -25,6 +25,7 @@ Classifier: Topic :: Database
25
25
  Classifier: Topic :: Software Development :: Documentation
26
26
  Requires-Python: >=3.11
27
27
  Requires-Dist: mini-racer>=0.14
28
+ Requires-Dist: pyyaml>=6
28
29
  Requires-Dist: resvg-py>=0.5
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: fonttools>=4.50; extra == 'dev'
@@ -75,7 +76,7 @@ Description-Content-Type: text/markdown
75
76
  <a href="https://qdbd.dev/#demo"><img src="https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/demo.gif" alt="Searching, focusing and opening a table in a qdbd diagram" width="760"></a>
76
77
  </p>
77
78
  <p align="center">
78
- <em><a href="https://qdbd.dev/#demo">Watch the full 60-second demo →</a></em>
79
+ <em><a href="https://qdbd.dev/#demo">Watch the full 75-second demo →</a></em>
79
80
  </p>
80
81
 
81
82
  ## What is qdbd?
@@ -109,9 +110,9 @@ Every demo is a single HTML page written by `qdbd build`, from a QuickDBD schema
109
110
  | | |
110
111
  |---|---|
111
112
  | [![200 tables](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthetic.png)](https://qdbd.dev/demo/synthetic/)<br>**[200 tables](https://qdbd.dev/demo/synthetic/)**<br>Five business domains, 200 tables and every relationship type. Search, filter, click a table to focus it. | [![Schema diff](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/diff.png)](https://qdbd.dev/demo/diff/)<br>**[Schema diff](https://qdbd.dev/demo/diff/)**<br>What a branch changed, coloured in: new tables in green, changed columns in amber, removals listed. |
112
- | [![AdventureWorks](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/adventureworks.png)](https://qdbd.dev/demo/adventureworks/)<br>**[AdventureWorks](https://qdbd.dev/demo/adventureworks/)**<br>Microsoft's bicycle-company OLTP database: 68 tables in five schemas, one region each, with column descriptions. | [![OMOP CDM on Synthea](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthea.png)](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. |
113
+ | [![OMOP CDM on Synthea](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthea.png)](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. | [![Skytrax reviews (dbt yml)](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/skytrax.png)](https://qdbd.dev/demo/skytrax/#t=dim_customer)<br>**[Skytrax reviews (dbt yml)](https://qdbd.dev/demo/skytrax/#t=dim_customer)**<br>Four star schemas, drawn by hand. One # @dbt: line adds the dbt yml's descriptions, pii tags and column types. Source: the qdbd-dbt-demo repo on GitHub. |
113
114
 
114
- [View all 19 demos →](https://qdbd.dev/demo/)
115
+ [View all 21 demos →](https://qdbd.dev/demo/)
115
116
  <!-- gallery:end -->
116
117
 
117
118
  ## Quickstart
@@ -193,7 +194,7 @@ jobs:
193
194
  permissions: { contents: read, pull-requests: write }
194
195
  steps:
195
196
  - uses: actions/checkout@v4
196
- - uses: MarkPhamm/quickdbd-cli@v0.5.0
197
+ - uses: MarkPhamm/quickdbd-cli@v0.6.1
197
198
  with:
198
199
  path: erd/ # schema file(s) or folder(s)
199
200
  output: site/index.html
@@ -277,7 +278,7 @@ You edit the text; the diagram is generated from it. Table positions are the one
277
278
  - [Install](https://qdbd.dev/install/) and [Quick Start](https://qdbd.dev/getting-started/)
278
279
  - [Schema Syntax](https://qdbd.dev/syntax/), [many files](https://qdbd.dev/files/), [table positions](https://qdbd.dev/layout/), [tags and colours](https://qdbd.dev/tags/)
279
280
  - [Reading the Diagram](https://qdbd.dev/reading-the-diagram/), [selectors](https://qdbd.dev/selectors/), [exports](https://qdbd.dev/exports/), [sharing a link](https://qdbd.dev/sharing/)
280
- - [dbt Projects](https://qdbd.dev/dbt/), with a [jaffle_shop walkthrough](https://qdbd.dev/example/)
281
+ - [dbt Projects](https://qdbd.dev/dbt/), with a [jaffle_shop walkthrough](https://qdbd.dev/example/) and an [example dbt project](https://github.com/MarkPhamm/qdbd-dbt-demo) whose hand-drawn ERD takes its docs from the yml
281
282
  - [AI Agents (MCP)](https://qdbd.dev/mcp/): `qdbd mcp` for Claude, Copilot and Cursor
282
283
  - [Schema Diffs](https://qdbd.dev/diffs/), the [GitHub Action](https://qdbd.dev/github-action/) and the [CLI reference](https://qdbd.dev/cli/)
283
284
  - [Development](https://qdbd.dev/development/): working on qdbd itself, releasing, and adding a demo
@@ -37,7 +37,7 @@
37
37
  <a href="https://qdbd.dev/#demo"><img src="https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/demo.gif" alt="Searching, focusing and opening a table in a qdbd diagram" width="760"></a>
38
38
  </p>
39
39
  <p align="center">
40
- <em><a href="https://qdbd.dev/#demo">Watch the full 60-second demo →</a></em>
40
+ <em><a href="https://qdbd.dev/#demo">Watch the full 75-second demo →</a></em>
41
41
  </p>
42
42
 
43
43
  ## What is qdbd?
@@ -71,9 +71,9 @@ Every demo is a single HTML page written by `qdbd build`, from a QuickDBD schema
71
71
  | | |
72
72
  |---|---|
73
73
  | [![200 tables](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthetic.png)](https://qdbd.dev/demo/synthetic/)<br>**[200 tables](https://qdbd.dev/demo/synthetic/)**<br>Five business domains, 200 tables and every relationship type. Search, filter, click a table to focus it. | [![Schema diff](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/diff.png)](https://qdbd.dev/demo/diff/)<br>**[Schema diff](https://qdbd.dev/demo/diff/)**<br>What a branch changed, coloured in: new tables in green, changed columns in amber, removals listed. |
74
- | [![AdventureWorks](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/adventureworks.png)](https://qdbd.dev/demo/adventureworks/)<br>**[AdventureWorks](https://qdbd.dev/demo/adventureworks/)**<br>Microsoft's bicycle-company OLTP database: 68 tables in five schemas, one region each, with column descriptions. | [![OMOP CDM on Synthea](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthea.png)](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. |
74
+ | [![OMOP CDM on Synthea](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthea.png)](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. | [![Skytrax reviews (dbt yml)](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/skytrax.png)](https://qdbd.dev/demo/skytrax/#t=dim_customer)<br>**[Skytrax reviews (dbt yml)](https://qdbd.dev/demo/skytrax/#t=dim_customer)**<br>Four star schemas, drawn by hand. One # @dbt: line adds the dbt yml's descriptions, pii tags and column types. Source: the qdbd-dbt-demo repo on GitHub. |
75
75
 
76
- [View all 19 demos →](https://qdbd.dev/demo/)
76
+ [View all 21 demos →](https://qdbd.dev/demo/)
77
77
  <!-- gallery:end -->
78
78
 
79
79
  ## Quickstart
@@ -155,7 +155,7 @@ jobs:
155
155
  permissions: { contents: read, pull-requests: write }
156
156
  steps:
157
157
  - uses: actions/checkout@v4
158
- - uses: MarkPhamm/quickdbd-cli@v0.5.0
158
+ - uses: MarkPhamm/quickdbd-cli@v0.6.1
159
159
  with:
160
160
  path: erd/ # schema file(s) or folder(s)
161
161
  output: site/index.html
@@ -239,7 +239,7 @@ You edit the text; the diagram is generated from it. Table positions are the one
239
239
  - [Install](https://qdbd.dev/install/) and [Quick Start](https://qdbd.dev/getting-started/)
240
240
  - [Schema Syntax](https://qdbd.dev/syntax/), [many files](https://qdbd.dev/files/), [table positions](https://qdbd.dev/layout/), [tags and colours](https://qdbd.dev/tags/)
241
241
  - [Reading the Diagram](https://qdbd.dev/reading-the-diagram/), [selectors](https://qdbd.dev/selectors/), [exports](https://qdbd.dev/exports/), [sharing a link](https://qdbd.dev/sharing/)
242
- - [dbt Projects](https://qdbd.dev/dbt/), with a [jaffle_shop walkthrough](https://qdbd.dev/example/)
242
+ - [dbt Projects](https://qdbd.dev/dbt/), with a [jaffle_shop walkthrough](https://qdbd.dev/example/) and an [example dbt project](https://github.com/MarkPhamm/qdbd-dbt-demo) whose hand-drawn ERD takes its docs from the yml
243
243
  - [AI Agents (MCP)](https://qdbd.dev/mcp/): `qdbd mcp` for Claude, Copilot and Cursor
244
244
  - [Schema Diffs](https://qdbd.dev/diffs/), the [GitHub Action](https://qdbd.dev/github-action/) and the [CLI reference](https://qdbd.dev/cli/)
245
245
  - [Development](https://qdbd.dev/development/): working on qdbd itself, releasing, and adding a demo
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quickdbd-cli"
7
- version = "0.5.0"
7
+ version = "0.6.1"
8
8
  description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -25,6 +25,7 @@ classifiers = [
25
25
  ]
26
26
  dependencies = [
27
27
  "mini-racer>=0.14",
28
+ "pyyaml>=6",
28
29
  "resvg-py>=0.5",
29
30
  ]
30
31
 
@@ -0,0 +1 @@
1
+ __version__ = "0.6.1"
@@ -286,10 +286,10 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
286
286
  /* doc-comment tooltip */
287
287
  .tip { position: absolute; display: none; max-width: 340px; background: var(--tip-bg); color: #eef0f2; border-radius: 8px;
288
288
  padding: 8px 10px; font-size: 12px; line-height: 1.45; pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.25);
289
- z-index: 5; }
289
+ z-index: 5; overflow-wrap: anywhere; }
290
290
  .tip.open { display: block; }
291
291
  .tip .tt { font-weight: bold; margin-bottom: 3px; }
292
- .tip .tt span { font-weight: normal; color: #9aa4ad; margin-left: 8px; }
292
+ .tip .tt span { font-weight: normal; color: #9aa4ad; margin-left: 8px; white-space: nowrap; }
293
293
  .tip .td { white-space: pre-wrap; color: #d8dde2; }
294
294
 
295
295
  .hint { position: absolute; left: 16px; bottom: 14px; color: var(--muted); font-size: 12px; }
@@ -79,6 +79,7 @@ Declare a relationship once, in either direction.
79
79
  - Tags give tables a role and a header colour: `# @tag: gold, pii` above a table. Names like `dim_`, `fct_`, `stg_`, `int_`, `raw_` are tagged automatically.
80
80
  - `# @color gold #b8860b` sets a tag's colour (anywhere in any file).
81
81
  - Columns take tags too, on or above the column line: `discount decimal # @tag: added`. The row is tinted in the tag's colour; `added` is green, `removed` is red and struck through. Use them to mark proposed column changes for review.
82
+ - `# @dbt: ../my_dbt_project` (anywhere in one file; the path is relative to that file) links a dbt project. Tables matching a model, seed, snapshot or source (`"source_name.table"`) by name get its yml description (shown before the comment), tags and `data_type` (for columns with no type). Where the schema and the yml disagree, `qdbd check` warns (`--strict` fails). `# @dbt: none` above a table skips it.
82
83
 
83
84
  ## Settings and layout sections
84
85
 
@@ -128,7 +129,7 @@ Ref: order_items.(order_id, region) > orders.(id, region)
128
129
  - Drawn: tables (`schema.table`, `public.users` = `users`), columns and types, `pk`, `null`, `unique`, `increment`, `default:`, indexes, `note`/`Note`, every `Ref` form (short, long, inline, composite, with `?` for an optional side), `TableGroup`, `headercolor`, `TablePartial`.
129
130
  - Accepted but not drawn: `Project`, `Enum` (the column shows the enum name as its type), `checks`, `records`, sticky notes, ref `delete:`/`update:`, `TableGroup` colours and notes.
130
131
  - `use` imports aren't followed: every file you give qdbd is read into one namespace.
131
- - `// @tag:` and `// @color` comments work; plain `//` comments are not documentation (use `note`).
132
+ - `// @tag:`, `// @color` and `// @dbt:` comments work; plain `//` comments are not documentation (use `note`).
132
133
 
133
134
  # Notes
134
135
 
@@ -193,7 +193,7 @@
193
193
  function fit(only, animate) {
194
194
  var b = Q.bounds(only ? Q.subset(S.vm, only) : S.vm, S.pos, S.mode), sz = stageSize(), pad = 40;
195
195
  if (S.regions && !only) b = [b[0] - 30, b[1] - 60, b[2] + 30, b[3] + 30]; // room for region borders and labels
196
- if (only) sz = [sz[0] - (details.classList.contains("open") ? 400 : 0), sz[1]]; // keep clear of the details panel
196
+ if (details.classList.contains("open")) sz = [Math.max(200, sz[0] - 400), sz[1]]; // the panel covers the right side
197
197
  // Keep clear of the floating search box / toolbar at the top and, on narrow screens, the toolbar at the bottom.
198
198
  var top = 70, bottom = sz[0] < 900 ? 70 : pad, availH = sz[1] - top - bottom;
199
199
  var k = Math.min((sz[0] - 2 * pad) / (b[2] - b[0]), availH / (b[3] - b[1]), 1.5);
@@ -58,14 +58,14 @@ def _symbols(schema: Schema) -> dict:
58
58
  tables = []
59
59
  for t in schema.tables.values():
60
60
  cols = [
61
- {"name": c.name, "line": c.line, "type": c.type, "pk": c.pk, "fk": c.fk}
61
+ {"name": c.name, "line": c.line, "type": c.shown_type(), "pk": c.pk, "fk": c.fk}
62
62
  for c in t.columns.values()
63
63
  if not c.virtual
64
64
  ]
65
65
  end = t.end_line or max([t.line + 1] + [c["line"] for c in cols])
66
66
  table = {"name": t.name, "aliases": t.aliases, "file": t.path, "line": t.line, "endLine": end, "columns": cols}
67
- if t.comments:
68
- table["doc"] = "\n".join(t.comments)
67
+ if t.doc():
68
+ table["doc"] = t.doc()
69
69
  tables.append(table)
70
70
 
71
71
  def end(e) -> dict:
@@ -406,13 +406,13 @@ def cmd_convert(args) -> int:
406
406
  root = next((p for p in inputs if p.is_dir() and f.resolve().is_relative_to(p.resolve())), None)
407
407
  rel = f.resolve().relative_to(root.resolve()).as_posix() if root else f.name
408
408
  sources.append((str(f), rel, f.read_text(encoding="utf-8")))
409
- schema = compile_sources([(d, t) for d, _, t in sources])
409
+ schema = compile_sources([(d, t) for d, _, t in sources], dbt=False)
410
410
  if not _print_diagnostics(schema, False):
411
411
  return 1
412
412
  res = convert.convert(sources, args.to, split_groups=to_dir)
413
413
  for w in res.warnings:
414
414
  print(w.format(), file=sys.stderr)
415
- again = compile_sources(list(res.files.items()))
415
+ again = compile_sources(list(res.files.items()), dbt=False)
416
416
  if again.errors or convert.canonical(again, res.lost) != convert.canonical(schema, res.lost):
417
417
  for d in again.errors:
418
418
  print(d.format(), file=sys.stderr)
@@ -38,7 +38,7 @@ class ConvertResult:
38
38
 
39
39
  def convert(sources: list[tuple[str, str, str]], to: str, split_groups: bool) -> ConvertResult:
40
40
  """``sources``: ``(display path, output-relative path, text)``. Files already in ``to`` are copied as is."""
41
- schema, parses = compile_files([(d, t) for d, _, t in sources])
41
+ schema, parses = compile_files([(d, t) for d, _, t in sources], dbt=False)
42
42
  res = ConvertResult()
43
43
  for t in schema.tables.values():
44
44
  if t.id in schema.layout:
@@ -88,7 +88,10 @@ def canonical(schema: Schema, lost: set[tuple]) -> dict:
88
88
  if ("col", t.name, c.name, k) in lost:
89
89
  del col[k]
90
90
  cols[c.name] = col
91
- table = {"cols": cols, "doc": _doc(t.comments), "aliases": t.aliases, "tags": t.tags, "dbt_tags": t.dbt_tags}
91
+ table = {
92
+ "cols": cols, "doc": _doc(t.comments), "aliases": t.aliases, "tags": t.tags, "dbt_tags": t.dbt_tags,
93
+ "dbt_skip": t.dbt_skip,
94
+ } # fmt: skip
92
95
  for k in list(table):
93
96
  if ("table", t.name, k) in lost:
94
97
  del table[k]
@@ -153,7 +156,9 @@ class _DbmlWriter:
153
156
  self.warn(line, f"settings: [settings] {k.capitalize()} has no DBML equivalent; dropped")
154
157
  for tag, (color, _) in sorted(fp.colors.items()):
155
158
  lines.append(f"// @color {tag} {color}")
156
- if fp.colors:
159
+ if fp.dbt: # shortcut: kept as written, so it breaks if -o puts the file in another folder
160
+ lines.append(f"// @dbt: {fp.dbt[0]}")
161
+ if fp.colors or fp.dbt:
157
162
  lines.append("")
158
163
  for t in (t for t in self.schema.tables.values() if t.path == fp.path):
159
164
  lines += self.table(t)
@@ -170,6 +175,8 @@ class _DbmlWriter:
170
175
  out.append(f"// @tag: {', '.join(t.tags)}")
171
176
  if t.dbt_tags:
172
177
  out.append(f"// @dbt-tags: {', '.join(t.dbt_tags)}")
178
+ if t.dbt_skip:
179
+ out.append("// @dbt: none")
173
180
  head = f"Table {_dbml_name(t.name)}"
174
181
  if t.aliases:
175
182
  head += f" as {_dbml_name(t.aliases[0])}"
@@ -313,6 +320,8 @@ class _QuickdbdWriter:
313
320
  header.append(f"# enum {name}: {', '.join(values)}")
314
321
  for tag, (color, _) in sorted(fp.colors.items()):
315
322
  header.append(f"# @color {tag} {color}")
323
+ if fp.dbt: # shortcut: kept as written, so it breaks if -o puts the file in another folder
324
+ header.append(f"# @dbt: {fp.dbt[0]}")
316
325
  if not self.split:
317
326
  for group, line in fp.groups.items():
318
327
  self.warn(line, f"TableGroup {group}: QuickDBD groups tables by file (use -o DIR to split); dropped")
@@ -347,6 +356,8 @@ class _QuickdbdWriter:
347
356
  out.append(f"# @tag: {', '.join(t.tags)}")
348
357
  if t.dbt_tags:
349
358
  out.append(f"# @dbt-tags: {', '.join(t.dbt_tags)}")
359
+ if t.dbt_skip:
360
+ out.append("# @dbt: none")
350
361
  out.append(_qdbd_name(t.name) + (f" as {', '.join(t.aliases)}" if t.aliases else ""))
351
362
  out.append("-")
352
363
  for c in t.columns.values():
@@ -16,7 +16,18 @@ import unicodedata
16
16
  from dataclasses import dataclass, field
17
17
 
18
18
  from .model import Column, Diagnostic, Table
19
- from .parser import COLOR_RE, HEX_RE, TAG_NAME_RE, TAG_RE, FileParse, PendingRel, encode, split_tags
19
+ from .parser import (
20
+ COLOR_RE,
21
+ DBT_RE,
22
+ HEX_RE,
23
+ TAG_NAME_RE,
24
+ TAG_RE,
25
+ FileParse,
26
+ PendingRel,
27
+ encode,
28
+ is_dbt_none,
29
+ split_tags,
30
+ )
20
31
 
21
32
  # Relationship operator -> (cardinality of the left end, of the right end). A `?` makes its own side optional.
22
33
  _BASE = {"-": ("one", "one"), "<>": ("many", "many"), ">": ("many", "one"), "<": ("one", "many")}
@@ -1287,7 +1298,9 @@ class _Interpreter:
1287
1298
  line = t.line - 1
1288
1299
  while line in self.comments and line not in self.code_lines:
1289
1300
  m = TAG_RE.match(self.comments[line])
1290
- if m:
1301
+ if is_dbt_none(self.comments[line]):
1302
+ t.dbt_skip = True
1303
+ elif m:
1291
1304
  target = t.dbt_tags if m.group(1).lower().startswith("dbt") else t.tags
1292
1305
  target[:0] = [x for x in split_tags(m.group(2)) if x not in target]
1293
1306
  line -= 1
@@ -1306,10 +1319,17 @@ class _Interpreter:
1306
1319
 
1307
1320
  def colors(self) -> None:
1308
1321
  first_code = min(self.code_lines, default=0)
1322
+
1323
+ def directive(text: str) -> bool:
1324
+ return bool(COLOR_RE.match(text) or (DBT_RE.match(text) and not is_dbt_none(text)))
1325
+
1309
1326
  self.fp.general_comments = [
1310
- text for line, text in sorted(self.comments.items()) if line < first_code and not COLOR_RE.match(text)
1327
+ text for line, text in sorted(self.comments.items()) if line < first_code and not directive(text)
1311
1328
  ]
1312
1329
  for line, text in sorted(self.comments.items()):
1330
+ d = DBT_RE.match(text)
1331
+ if d and not is_dbt_none(text):
1332
+ self.fp.set_dbt(d.group(1), line)
1313
1333
  m = COLOR_RE.match(text)
1314
1334
  if not m:
1315
1335
  continue
@@ -174,10 +174,12 @@ def _comment_lines(text: str) -> list[str]:
174
174
  return out
175
175
 
176
176
 
177
- def _node_tags(n: dict) -> list[str]:
177
+ def node_tags(n: dict) -> list[str]:
178
178
  """dbt tags (node-level and config), lowercased, in order, usable as qdbd tag names."""
179
179
  out: list[str] = []
180
- raw = list(n.get("tags") or []) + list((n.get("config") or {}).get("tags") or [])
180
+ raw: list = []
181
+ for v in (n.get("tags"), (n.get("config") or {}).get("tags")):
182
+ raw += [v] if isinstance(v, str) else list(v or []) # yml allows `tags: pii`
181
183
  for tag in raw:
182
184
  tag = re.sub(r"[^a-z0-9_.-]+", "-", str(tag).lower()).strip("-.")
183
185
  if tag and tag not in out:
@@ -243,7 +245,7 @@ class _Importer:
243
245
  name = self._base_name(n)
244
246
  if names[name] > 1: # same name in two packages
245
247
  name = f"{n.get('package_name')}.{name}"
246
- t = Tbl(n["unique_id"], name, self._group(n), n.get("description") or "", tags=_node_tags(n))
248
+ t = Tbl(n["unique_id"], name, self._group(n), n.get("description") or "", tags=node_tags(n))
247
249
  self._columns(t, n)
248
250
  self.tables[t.uid] = t
249
251
  self.by_name.setdefault(n.get("name", "").lower(), []).append(t.uid)
@@ -0,0 +1,275 @@
1
+ """`# @dbt: PATH`: descriptions, tags and types from a dbt project's yml, for hand-written schema files.
2
+
3
+ No manifest.json is needed (dbt Cloud projects have none locally). qdbd reads ``dbt_project.yml`` and the yml, md
4
+ and sql files under its resource paths (the root project only; ``dbt_packages/`` and ``target/`` are skipped).
5
+
6
+ - Tables match models, seeds, snapshots and sources (``source_name.table_name``) by name, ignoring case;
7
+ columns match by name inside the matched table. ``# @dbt: none`` above a table opts it out.
8
+ - The dbt description is shown before the schema file's own comment; ``{{ doc('x') }}`` is filled from
9
+ ``{% docs x %}`` blocks. Model and column tags rank below ``# @tag:`` and name prefixes. ``data_type`` fills
10
+ columns that have no type.
11
+ - Drift is reported as warnings (``--strict`` fails on them): a table dbt doesn't have, a column only one side has,
12
+ a type that differs (by base type: ``varchar(50)`` and ``string`` agree).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import re
19
+ from collections.abc import Callable
20
+ from dataclasses import dataclass, field
21
+ from pathlib import Path
22
+
23
+ import yaml
24
+
25
+ from .dbt import node_tags, norm_type
26
+ from .model import DbtInfo, Schema
27
+
28
+ SKIP_DIRS = {"dbt_packages", "dbt_modules", "target", "logs", "node_modules"}
29
+ WATCHED = (".yml", ".yaml", ".md", ".sql", ".py", ".csv")
30
+ _DOC_REF = re.compile(r"""\{\{\s*doc\(\s*(?:['"][^'"]*['"]\s*,\s*)?['"]([^'"]+)['"]\s*\)\s*\}\}""")
31
+ _DOCS_BLOCK = re.compile(r"\{%-?\s*docs\s+([\w.-]+)\s*-?%\}(.*?)\{%-?\s*enddocs\s*-?%\}", re.S)
32
+ _SNAPSHOT = re.compile(r"\{%-?\s*snapshot\s+(\w+)\s*-?%\}")
33
+
34
+ # Base types that mean the same thing across warehouses; sizes and precision are ignored.
35
+ _FAMILIES = {
36
+ "int": "int integer int2 int4 int8 int16 int32 int64 smallint bigint tinyint byteint mediumint hugeint long short",
37
+ "decimal": "decimal numeric number dec bignumeric",
38
+ "float": "float float4 float8 float32 float64 double double_precision real",
39
+ "string": "string text varchar char character nvarchar nchar bpchar varchar2 nvarchar2 citext clob",
40
+ "bool": "bool boolean",
41
+ "timestamp": "timestamp timestamptz datetime datetime2 smalldatetime datetimeoffset timestamp_ntz timestamp_ltz "
42
+ "timestamp_tz",
43
+ "time": "time timetz",
44
+ "json": "json jsonb variant object super",
45
+ "binary": "binary varbinary bytes bytea blob",
46
+ "uuid": "uuid uniqueidentifier",
47
+ "array": "array list",
48
+ }
49
+ _BASE = {name: family for family, names in _FAMILIES.items() for name in names.split()}
50
+
51
+
52
+ class DbtError(Exception):
53
+ pass
54
+
55
+
56
+ @dataclass
57
+ class DbtColumn:
58
+ name: str
59
+ description: str
60
+ data_type: str | None
61
+ tags: list[str]
62
+ line: int
63
+
64
+
65
+ @dataclass
66
+ class DbtModel:
67
+ name: str # model / seed / snapshot name, or `source_name.table_name`
68
+ description: str
69
+ tags: list[str]
70
+ columns: dict[str, DbtColumn] # by lowercased name
71
+ path: str # yml file, relative to the project root
72
+ line: int
73
+
74
+
75
+ @dataclass
76
+ class DbtProject:
77
+ root: Path
78
+ models: dict[str, DbtModel] = field(default_factory=dict) # by lowercased name
79
+ known: set[str] = field(default_factory=set) # lowercased names that exist as files, documented or not
80
+ ambiguous: set[str] = field(default_factory=set)
81
+ docs: dict[str, str] = field(default_factory=dict) # `{% docs name %}` blocks
82
+
83
+
84
+ class _Loader(getattr(yaml, "CSafeLoader", yaml.SafeLoader)): # type: ignore[misc]
85
+ """Records each mapping's line as `__line__`, so drift messages can point into the yml."""
86
+
87
+ def construct_mapping(self, node, deep=False):
88
+ m = super().construct_mapping(node, deep=deep)
89
+ m["__line__"] = node.start_mark.line + 1
90
+ return m
91
+
92
+
93
+ # (path, parser) -> (mtime_ns, parsed): keeps `qdbd serve` recompiles cheap
94
+ _cache: dict[tuple[Path, Callable], tuple[int, object]] = {}
95
+
96
+
97
+ def _cached(path: Path, parse: Callable[[str], object]) -> object:
98
+ mtime = path.stat().st_mtime_ns
99
+ hit = _cache.get((path, parse))
100
+ if hit is None or hit[0] != mtime:
101
+ hit = _cache[(path, parse)] = (mtime, parse(path.read_text(encoding="utf-8")))
102
+ return hit[1]
103
+
104
+
105
+ def _load_yaml(text: str) -> object:
106
+ return yaml.load(text, Loader=_Loader) # a SafeLoader: no arbitrary Python objects
107
+
108
+
109
+ def _yaml(path: Path, root: Path) -> object:
110
+ try:
111
+ return _cached(path, _load_yaml)
112
+ except yaml.YAMLError as e:
113
+ mark = getattr(e, "problem_mark", None)
114
+ where = f"{path.relative_to(root).as_posix()}:{mark.line + 1}" if mark else path.relative_to(root).as_posix()
115
+ raise DbtError(f"{where}: invalid YAML ({getattr(e, 'problem', None) or e})") from None
116
+
117
+
118
+ def find_root(path: Path) -> Path:
119
+ """The dbt project a `# @dbt:` path names: its folder, or its dbt_project.yml."""
120
+ root = path.parent if path.name == "dbt_project.yml" else path
121
+ if not (root / "dbt_project.yml").is_file():
122
+ raise DbtError(f"no dbt_project.yml in {root}")
123
+ return root
124
+
125
+
126
+ def _paths(root: Path) -> tuple[dict[str, list[Path]], list[Path]]:
127
+ """(resource kind -> folders, docs folders), from dbt_project.yml with dbt's defaults."""
128
+ cfg = _yaml(root / "dbt_project.yml", root)
129
+ cfg = cfg if isinstance(cfg, dict) else {}
130
+
131
+ def get(key: str, old: str | None = None, default: str | None = None) -> list[Path]:
132
+ v = cfg.get(key) or (cfg.get(old) if old else None) or ([default] if default else [])
133
+ out = []
134
+ for p in [v] if isinstance(v, str) else v:
135
+ # dbt resolves these inside the project; anything else (`/`, `../..`) would walk the whole disk
136
+ if not (root / str(p)).resolve().is_relative_to(root.resolve()):
137
+ raise DbtError(f"dbt_project.yml: {key} '{p}' is outside the project")
138
+ out.append(root / str(p))
139
+ return out
140
+
141
+ kinds = {
142
+ "model": get("model-paths", "source-paths", "models"),
143
+ "seed": get("seed-paths", "data-paths", "seeds"),
144
+ "snapshot": get("snapshot-paths", None, "snapshots"),
145
+ }
146
+ doc_dirs = get("docs-paths")
147
+ if not doc_dirs: # dbt's default: every resource path
148
+ doc_dirs = [d for ds in kinds.values() for d in ds]
149
+ doc_dirs += get("analysis-paths", None, "analyses") + get("macro-paths", None, "macros")
150
+ return kinds, doc_dirs
151
+
152
+
153
+ def _walk(dirs: list[Path], suffixes: tuple[str, ...]) -> list[Path]:
154
+ out: dict[Path, None] = {}
155
+ for d in dirs:
156
+ for dirpath, dirnames, filenames in os.walk(d):
157
+ dirnames[:] = sorted(n for n in dirnames if not n.startswith(".") and n not in SKIP_DIRS)
158
+ for n in sorted(filenames):
159
+ if n.lower().endswith(suffixes):
160
+ out[Path(dirpath, n)] = None
161
+ return list(out)
162
+
163
+
164
+ def watched_files(root: Path) -> list[Path]:
165
+ """Every file `load_project` reads (or may read once it exists), for `qdbd serve`'s change polling."""
166
+ kinds, doc_dirs = _paths(root)
167
+ return [root / "dbt_project.yml", *_walk([d for ds in kinds.values() for d in ds] + doc_dirs, WATCHED)]
168
+
169
+
170
+ def _text(v: object) -> str:
171
+ return str(v).strip() if v is not None else ""
172
+
173
+
174
+ def _column_name(v: object) -> str:
175
+ return _text(v).strip('"`')
176
+
177
+
178
+ def load_project(root: Path) -> DbtProject:
179
+ kinds, doc_dirs = _paths(root)
180
+ project = DbtProject(root)
181
+ for p in _walk(kinds["model"], (".sql", ".py")):
182
+ project.known.add(p.stem.lower())
183
+ for p in _walk(kinds["seed"], (".csv",)):
184
+ project.known.add(p.stem.lower())
185
+ for p in _walk(kinds["snapshot"], (".sql",)):
186
+ project.known.update(m.lower() for m in _cached(p, _SNAPSHOT.findall)) # type: ignore[union-attr]
187
+ for p in _walk(doc_dirs, (".md",)):
188
+ for name, body in _cached(p, _DOCS_BLOCK.findall): # type: ignore[union-attr]
189
+ project.docs[name] = body.strip()
190
+ for p in _walk([d for ds in kinds.values() for d in ds], (".yml", ".yaml")):
191
+ data = _yaml(p, root)
192
+ if not isinstance(data, dict):
193
+ continue
194
+ rel = p.relative_to(root).as_posix()
195
+ for key in ("models", "seeds", "snapshots"):
196
+ for n in data.get(key) or []:
197
+ if isinstance(n, dict) and n.get("name"):
198
+ _add(project, _text(n["name"]), n, rel)
199
+ for src in data.get("sources") or []:
200
+ if not isinstance(src, dict) or not src.get("name"):
201
+ continue
202
+ for n in src.get("tables") or []:
203
+ if isinstance(n, dict) and n.get("name"):
204
+ _add(project, f"{_text(src['name'])}.{_text(n['name'])}", n, rel)
205
+ return project
206
+
207
+
208
+ def _add(project: DbtProject, name: str, n: dict, rel: str) -> None:
209
+ key = name.lower()
210
+ if key in project.models:
211
+ project.ambiguous.add(key)
212
+ return
213
+ cols: dict[str, DbtColumn] = {}
214
+ for c in n.get("columns") or []:
215
+ if isinstance(c, dict) and _column_name(c.get("name")):
216
+ cname = _column_name(c["name"])
217
+ dtype = _text(c.get("data_type")) or None
218
+ cols[cname.lower()] = DbtColumn(cname, _text(c.get("description")), dtype, node_tags(c), c["__line__"])
219
+ project.models[key] = DbtModel(name, _text(n.get("description")), node_tags(n), cols, rel, n["__line__"])
220
+ project.known.add(key)
221
+
222
+
223
+ def base_type(t: str) -> str:
224
+ """`varchar(255)` -> `string`, `INTEGER` -> `int`: what two types must share to count as the same."""
225
+ s = re.split(r"[(<\[]", (norm_type(t) or "").lower())[0].strip("_ ")
226
+ return _BASE.get(s, s)
227
+
228
+
229
+ Report = Callable[[str, int, str, str], None] # (path, line, message, severity)
230
+
231
+
232
+ def apply(schema: Schema, project: DbtProject, report: Report) -> None:
233
+ """Attach dbt descriptions, tags and types to the matching tables and columns, and report drift."""
234
+
235
+ def doc(text: str, path: str, line: int) -> str:
236
+ def sub(m: re.Match) -> str:
237
+ if m.group(1) in project.docs:
238
+ return project.docs[m.group(1)]
239
+ report(path, line, f"dbt: no docs block named '{m.group(1)}'", "warning")
240
+ return m.group(0)
241
+
242
+ return _DOC_REF.sub(sub, text)
243
+
244
+ for t in schema.tables.values():
245
+ key = t.name.lower()
246
+ if t.dbt_skip or key in project.ambiguous:
247
+ continue
248
+ m = project.models.get(key)
249
+ if m is None:
250
+ if key not in project.known:
251
+ report(
252
+ t.path,
253
+ t.line,
254
+ f"dbt: no model, seed, snapshot or source named '{t.name}' "
255
+ "(put '# @dbt: none' above the table if it isn't one)",
256
+ "warning",
257
+ )
258
+ continue
259
+ t.dbt = DbtInfo(doc(m.description, t.path, t.line), list(m.tags))
260
+ cols = {c.name.lower(): c for c in t.columns.values() if not c.virtual}
261
+ for ckey, dc in m.columns.items():
262
+ c = cols.get(ckey)
263
+ if c is None:
264
+ msg = f"dbt: column '{dc.name}' ({m.path}:{dc.line}) is missing from '{t.name}'"
265
+ report(t.path, t.line, msg, "warning")
266
+ continue
267
+ c.dbt = DbtInfo(doc(dc.description, t.path, c.line), list(dc.tags), norm_type(dc.data_type))
268
+ if dc.data_type and c.type and not c.type_default and base_type(c.type) != base_type(dc.data_type):
269
+ msg = f"dbt: type '{c.type}' differs from data_type '{dc.data_type}' ({m.path}:{dc.line})"
270
+ report(t.path, c.line, msg, "warning")
271
+ if m.columns:
272
+ for ckey, c in cols.items():
273
+ if ckey not in m.columns:
274
+ msg = f"dbt: column '{c.name}' is not in the yml for '{m.name}' ({m.path}:{m.line})"
275
+ report(t.path, c.line, msg, "warning")
@@ -175,7 +175,7 @@ def sources_at_ref(ref: str, inputs: list[Path]) -> list[tuple[str, str]]:
175
175
 
176
176
 
177
177
  def schema_at_ref(ref: str, inputs: list[Path]) -> Schema:
178
- return compile_sources(sources_at_ref(ref, inputs))
178
+ return compile_sources(sources_at_ref(ref, inputs), dbt=False)
179
179
 
180
180
 
181
181
  # --------------------------------------------------------------------------- output