sql-glider 0.1.24__py3-none-any.whl → 0.1.26__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-glider
3
- Version: 0.1.24
3
+ Version: 0.1.26
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/
@@ -1,6 +1,6 @@
1
1
  sqlglider/__init__.py,sha256=gDf7s52dMcX7JuCZ1SLawcB1vb3U0yJCohu9RQAATBY,125
2
- sqlglider/_version.py,sha256=IV4a2R7tlzuACf6FAyPEbprLKNroeE-n_UPSKi1QJSc,706
3
- sqlglider/cli.py,sha256=oML-M3PW8Cjddii4_Tx_tfYAxYh6Q-inU4eehx0gzC0,69570
2
+ sqlglider/_version.py,sha256=Y9o7KiJWiG6n9XbSpMICgNgajFRbL4an-gN1BQc-jwM,706
3
+ sqlglider/cli.py,sha256=If-USLXwbTcP_ehacyPcahD5aygmkqI9ycxJDqIpalg,69701
4
4
  sqlglider/global_models.py,sha256=2vyJXAuXOsXQpE-D3F0ejj7eR9z0nDWFjTkielhzM8k,356
5
5
  sqlglider/catalog/__init__.py,sha256=2PqFPyzFXJ14FpSUcBmVK2L-a_ypWQHAbHFHxLDk_LE,814
6
6
  sqlglider/catalog/base.py,sha256=R7htHC43InpH4uRjYk33dMYYji6oylHns7Ye_mgfjJE,3116
@@ -12,7 +12,7 @@ sqlglider/dissection/formatters.py,sha256=M7gsmTNljRIeLIRv4D0vHvqJVrTqWSpsg7vem8
12
12
  sqlglider/dissection/models.py,sha256=RRD3RIteqbUBY6e-74skKDvMH3qeAUaqA2sFcrjP5GQ,3618
13
13
  sqlglider/graph/__init__.py,sha256=4DDdrPM75CmeQWt7wHdBsjCm1s70BHGLYdijIbaUEKY,871
14
14
  sqlglider/graph/builder.py,sha256=VNBdsDlkiaId3JGvr2G4h6OIFek_9zPsGMIYL9GpJlk,15796
15
- sqlglider/graph/diagram_formatters.py,sha256=6EdNwU23k4CL3hSBtM1lgZ5Gyc95o7vUMRdQa-a1Fko,22021
15
+ sqlglider/graph/diagram_formatters.py,sha256=S8K0pjSzRTcwT1yi7QJUP1gdfPR2p3Om93bVdtJZuxA,26465
16
16
  sqlglider/graph/formatters.py,sha256=p85-WN9oPmEETsAtWSo1sIQELF36w85QoFEJyfBZGoM,4800
17
17
  sqlglider/graph/merge.py,sha256=uUZlm4BN3S9gRL66Cc2mzhbtuh4SVAv2n4cN4eUEQBU,4077
18
18
  sqlglider/graph/models.py,sha256=EYmjv_WzDSNp_WfhJ6H-qBIOkAcoNKS7GRUryfKrHuY,9330
@@ -32,8 +32,8 @@ sqlglider/utils/__init__.py,sha256=KGp9-UzKz_OFBOTFoSy-g-NXDZsvyWXG_9-1zcC6ePE,2
32
32
  sqlglider/utils/config.py,sha256=qx5zE9pjLCCzHQDFVPLVd7LgJ-lghxUa2x-aZOAHByY,4962
33
33
  sqlglider/utils/file_utils.py,sha256=5_ff28E0r1R7emZzsOnRuHd-7zIX6873eyr1SuPEr4E,1093
34
34
  sqlglider/utils/schema.py,sha256=LiWrYDunXKJdoSlpKmIaIQ2hLSaIN1iQHqkXjMpGzRE,1883
35
- sql_glider-0.1.24.dist-info/METADATA,sha256=5giBXU7edyyoMBfbFVficYatA60-To2D_P3eMc6PdQQ,31197
36
- sql_glider-0.1.24.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
- sql_glider-0.1.24.dist-info/entry_points.txt,sha256=HDuakHqHS5C0HFKsMIxMYmDU7-BLBGrnIJcYaVRu-s0,251
38
- sql_glider-0.1.24.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
39
- sql_glider-0.1.24.dist-info/RECORD,,
35
+ sql_glider-0.1.26.dist-info/METADATA,sha256=xgItNGWc-KIxIsKFKj-sC6Dl1rCPuMuEyzk7XeDi2a4,31197
36
+ sql_glider-0.1.26.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
+ sql_glider-0.1.26.dist-info/entry_points.txt,sha256=HDuakHqHS5C0HFKsMIxMYmDU7-BLBGrnIJcYaVRu-s0,251
38
+ sql_glider-0.1.26.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
39
+ sql_glider-0.1.26.dist-info/RECORD,,
sqlglider/_version.py CHANGED
@@ -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.24'
32
- __version_tuple__ = version_tuple = (0, 1, 24)
31
+ __version__ = version = '0.1.26'
32
+ __version_tuple__ = version_tuple = (0, 1, 26)
33
33
 
34
34
  __commit_id__ = commit_id = None
sqlglider/cli.py CHANGED
@@ -1715,11 +1715,15 @@ def graph_query(
1715
1715
  elif output_format == "mermaid":
1716
1716
  from sqlglider.graph.diagram_formatters import MermaidFormatter
1717
1717
 
1718
- print(MermaidFormatter.format_query_result(result))
1718
+ print(MermaidFormatter.format_query_result(result, graph=querier.graph))
1719
1719
  elif output_format == "mermaid-markdown":
1720
1720
  from sqlglider.graph.diagram_formatters import MermaidMarkdownFormatter
1721
1721
 
1722
- print(MermaidMarkdownFormatter.format_query_result(result))
1722
+ print(
1723
+ MermaidMarkdownFormatter.format_query_result(
1724
+ result, graph=querier.graph
1725
+ )
1726
+ )
1723
1727
  elif output_format == "dot":
1724
1728
  from sqlglider.graph.diagram_formatters import DotFormatter
1725
1729
 
@@ -1727,7 +1731,7 @@ def graph_query(
1727
1731
  elif output_format == "plotly":
1728
1732
  from sqlglider.graph.diagram_formatters import PlotlyFormatter
1729
1733
 
1730
- print(PlotlyFormatter.format_query_result(result))
1734
+ print(PlotlyFormatter.format_query_result(result, graph=querier.graph))
1731
1735
 
1732
1736
  except ImportError as e:
1733
1737
  err_console.print(f"[red]Error:[/red] {e}")
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  import re
5
5
  from collections import defaultdict
6
- from typing import Any, Set
6
+ from typing import Any, Optional, Set
7
7
 
8
8
  from sqlglider.graph.models import LineageGraph
9
9
  from sqlglider.graph.query import LineageQueryResult
@@ -102,16 +102,26 @@ class MermaidFormatter:
102
102
  node_id = _sanitize_mermaid_id(node.identifier)
103
103
  lines.append(f' {node_id}["{node.identifier}"]')
104
104
 
105
- # Add edges
105
+ # Add edges with file path labels
106
106
  for edge in graph.edges:
107
107
  src = _sanitize_mermaid_id(edge.source_node)
108
108
  tgt = _sanitize_mermaid_id(edge.target_node)
109
- lines.append(f" {src} --> {tgt}")
109
+ # Extract filename from path
110
+ file_name = (
111
+ edge.file_path.split("/")[-1].split("\\")[-1] if edge.file_path else ""
112
+ )
113
+ if file_name:
114
+ lines.append(f" {src} -->|{file_name}| {tgt}")
115
+ else:
116
+ lines.append(f" {src} --> {tgt}")
110
117
 
111
118
  return "\n".join(lines)
112
119
 
113
120
  @staticmethod
114
- def format_query_result(result: LineageQueryResult) -> str:
121
+ def format_query_result(
122
+ result: LineageQueryResult,
123
+ graph: Optional[LineageGraph] = None,
124
+ ) -> str:
115
125
  """Format query result as a Mermaid flowchart with styling.
116
126
 
117
127
  The queried column is highlighted in amber, root nodes in teal,
@@ -119,6 +129,7 @@ class MermaidFormatter:
119
129
 
120
130
  Args:
121
131
  result: LineageQueryResult from upstream/downstream query
132
+ graph: Optional LineageGraph for edge file path labels
122
133
 
123
134
  Returns:
124
135
  Mermaid diagram string with style directives and legend
@@ -134,16 +145,27 @@ class MermaidFormatter:
134
145
  all_nodes = _collect_query_nodes(result)
135
146
  edges = _collect_query_edges(result)
136
147
 
148
+ # Build edge file path lookup if graph is provided
149
+ edge_file_paths: dict[tuple[str, str], str] = {}
150
+ if graph:
151
+ for e in graph.edges:
152
+ edge_file_paths[(e.source_node, e.target_node)] = e.file_path
153
+
137
154
  # Declare nodes
138
155
  for identifier in sorted(all_nodes):
139
156
  node_id = _sanitize_mermaid_id(identifier)
140
157
  lines.append(f' {node_id}["{identifier}"]')
141
158
 
142
- # Add edges
159
+ # Add edges with optional file path labels
143
160
  for src, tgt in sorted(edges):
144
- lines.append(
145
- f" {_sanitize_mermaid_id(src)} --> {_sanitize_mermaid_id(tgt)}"
146
- )
161
+ src_id = _sanitize_mermaid_id(src)
162
+ tgt_id = _sanitize_mermaid_id(tgt)
163
+ file_path = edge_file_paths.get((src, tgt), "")
164
+ file_name = file_path.split("/")[-1].split("\\")[-1] if file_path else ""
165
+ if file_name:
166
+ lines.append(f" {src_id} -->|{file_name}| {tgt_id}")
167
+ else:
168
+ lines.append(f" {src_id} --> {tgt_id}")
147
169
 
148
170
  # Style directives
149
171
  queried_id = _sanitize_mermaid_id(result.query_column)
@@ -200,16 +222,20 @@ class MermaidMarkdownFormatter:
200
222
  return f"```mermaid\n{mermaid}\n```"
201
223
 
202
224
  @staticmethod
203
- def format_query_result(result: LineageQueryResult) -> str:
225
+ def format_query_result(
226
+ result: LineageQueryResult,
227
+ graph: Optional[LineageGraph] = None,
228
+ ) -> str:
204
229
  """Format query result as a Mermaid diagram in a markdown code block.
205
230
 
206
231
  Args:
207
232
  result: LineageQueryResult from upstream/downstream query
233
+ graph: Optional LineageGraph for edge file path labels
208
234
 
209
235
  Returns:
210
236
  Markdown string with fenced Mermaid diagram
211
237
  """
212
- mermaid = MermaidFormatter.format_query_result(result)
238
+ mermaid = MermaidFormatter.format_query_result(result, graph=graph)
213
239
  return f"```mermaid\n{mermaid}\n```"
214
240
 
215
241
 
@@ -335,6 +361,8 @@ class DotFormatter:
335
361
  def _compute_layered_layout(
336
362
  nodes: list[str],
337
363
  edges: list[tuple[str, str]],
364
+ x_spacing: float = 250.0,
365
+ y_spacing: float = 100.0,
338
366
  ) -> dict[str, tuple[float, float]]:
339
367
  """Compute layered layout positions for nodes using topological ordering.
340
368
 
@@ -345,6 +373,8 @@ def _compute_layered_layout(
345
373
  Args:
346
374
  nodes: List of node identifiers
347
375
  edges: List of (source, target) edge tuples
376
+ x_spacing: Horizontal spacing between layers (default: 250 pixels)
377
+ y_spacing: Vertical spacing between nodes in the same layer (default: 100 pixels)
348
378
 
349
379
  Returns:
350
380
  Dictionary mapping node identifiers to (x, y) positions
@@ -395,15 +425,13 @@ def _compute_layered_layout(
395
425
 
396
426
  # Compute positions: x based on layer, y spread vertically within layer
397
427
  positions: dict[str, tuple[float, float]] = {}
398
- max_layer = max(layers.values()) if layers else 0
399
- x_spacing = 1.0 if max_layer == 0 else 1.0
400
428
 
401
429
  for layer, layer_nodes in layer_groups.items():
402
430
  x = layer * x_spacing
403
431
  n = len(layer_nodes)
404
432
  for i, node in enumerate(sorted(layer_nodes)):
405
- # Center nodes vertically, spread them out
406
- y = (i - (n - 1) / 2) * 0.5
433
+ # Center nodes vertically, spread them out uniformly
434
+ y = (i - (n - 1) / 2) * y_spacing
407
435
  positions[node] = (x, y)
408
436
 
409
437
  return positions
@@ -444,6 +472,11 @@ class PlotlyFormatter:
444
472
  node_ids = [n.identifier for n in graph.nodes]
445
473
  edge_tuples = [(e.source_node, e.target_node) for e in graph.edges]
446
474
 
475
+ # Build lookup for edge file paths
476
+ edge_file_paths: dict[tuple[str, str], str] = {}
477
+ for e in graph.edges:
478
+ edge_file_paths[(e.source_node, e.target_node)] = e.file_path
479
+
447
480
  if not node_ids:
448
481
  # Empty graph
449
482
  figure: dict[str, Any] = {
@@ -459,24 +492,46 @@ class PlotlyFormatter:
459
492
 
460
493
  positions = _compute_layered_layout(node_ids, edge_tuples)
461
494
 
462
- # Build edge traces (one trace per edge for simplicity)
495
+ # Build edge traces with hover text for file path
463
496
  edge_traces: list[dict[str, Any]] = []
497
+ edge_annotations: list[dict[str, Any]] = []
464
498
  for src, tgt in edge_tuples:
465
499
  if src in positions and tgt in positions:
466
500
  x0, y0 = positions[src]
467
501
  x1, y1 = positions[tgt]
502
+ file_path = edge_file_paths.get((src, tgt), "")
503
+ # Extract just the filename for display
504
+ file_name = (
505
+ file_path.split("/")[-1].split("\\")[-1] if file_path else ""
506
+ )
507
+
468
508
  edge_traces.append(
469
509
  {
470
510
  "type": "scatter",
471
511
  "x": [x0, x1, None],
472
512
  "y": [y0, y1, None],
473
513
  "mode": "lines",
474
- "line": {"width": 1, "color": "#888"},
475
- "hoverinfo": "none",
514
+ "line": {"width": 1.5, "color": "#888"},
515
+ "hoverinfo": "text",
516
+ "hovertext": file_path,
476
517
  "showlegend": False,
477
518
  }
478
519
  )
479
520
 
521
+ # Add annotation at midpoint of edge
522
+ mid_x = (x0 + x1) / 2
523
+ mid_y = (y0 + y1) / 2
524
+ edge_annotations.append(
525
+ {
526
+ "x": mid_x,
527
+ "y": mid_y,
528
+ "text": file_name,
529
+ "showarrow": False,
530
+ "font": {"size": 9, "color": "#666"},
531
+ "bgcolor": "rgba(255,255,255,0.8)",
532
+ }
533
+ )
534
+
480
535
  # Build node trace
481
536
  node_x = [positions[n][0] for n in node_ids if n in positions]
482
537
  node_y = [positions[n][1] for n in node_ids if n in positions]
@@ -489,15 +544,21 @@ class PlotlyFormatter:
489
544
  "mode": "markers+text",
490
545
  "text": node_text,
491
546
  "textposition": "top center",
547
+ "textfont": {"size": 11},
492
548
  "hoverinfo": "text",
493
549
  "marker": {
494
- "size": 20,
550
+ "size": 15,
495
551
  "color": "#6495ED",
496
552
  "line": {"width": 2, "color": "#4169E1"},
497
553
  },
498
554
  "showlegend": False,
499
555
  }
500
556
 
557
+ # Calculate figure dimensions based on graph size
558
+ min_height = 400
559
+ height_per_node = 50
560
+ calculated_height = max(min_height, len(node_ids) * height_per_node)
561
+
501
562
  figure = {
502
563
  "data": edge_traces + [node_trace],
503
564
  "layout": {
@@ -506,14 +567,19 @@ class PlotlyFormatter:
506
567
  "hovermode": "closest",
507
568
  "xaxis": {"visible": False},
508
569
  "yaxis": {"visible": False},
509
- "margin": {"l": 40, "r": 40, "t": 60, "b": 40},
570
+ "height": calculated_height,
571
+ "margin": {"l": 50, "r": 50, "t": 60, "b": 40},
572
+ "annotations": edge_annotations,
510
573
  },
511
574
  }
512
575
 
513
576
  return json.dumps(figure, indent=2)
514
577
 
515
578
  @staticmethod
516
- def format_query_result(result: LineageQueryResult) -> str:
579
+ def format_query_result(
580
+ result: LineageQueryResult,
581
+ graph: Optional[LineageGraph] = None,
582
+ ) -> str:
517
583
  """Format query result as a Plotly JSON figure with styling.
518
584
 
519
585
  The queried column is highlighted in amber, root nodes in teal,
@@ -521,6 +587,7 @@ class PlotlyFormatter:
521
587
 
522
588
  Args:
523
589
  result: LineageQueryResult from upstream/downstream query
590
+ graph: Optional LineageGraph for edge file path labels
524
591
 
525
592
  Returns:
526
593
  JSON string representing a Plotly figure specification
@@ -530,6 +597,12 @@ class PlotlyFormatter:
530
597
  all_nodes = _collect_query_nodes(result)
531
598
  edges = _collect_query_edges(result)
532
599
 
600
+ # Build edge file path lookup if graph is provided
601
+ edge_file_paths: dict[tuple[str, str], str] = {}
602
+ if graph:
603
+ for e in graph.edges:
604
+ edge_file_paths[(e.source_node, e.target_node)] = e.file_path
605
+
533
606
  if not all_nodes:
534
607
  # Should not happen, but handle gracefully
535
608
  figure: dict[str, Any] = {
@@ -568,12 +641,18 @@ class PlotlyFormatter:
568
641
  else:
569
642
  node_colors.append("#6495ED") # Default blue
570
643
 
571
- # Build edge traces
644
+ # Build edge traces with optional file path labels
572
645
  edge_traces: list[dict[str, Any]] = []
646
+ edge_annotations: list[dict[str, Any]] = []
573
647
  for src, tgt in sorted(edges):
574
648
  if src in positions and tgt in positions:
575
649
  x0, y0 = positions[src]
576
650
  x1, y1 = positions[tgt]
651
+ file_path = edge_file_paths.get((src, tgt), "")
652
+ file_name = (
653
+ file_path.split("/")[-1].split("\\")[-1] if file_path else ""
654
+ )
655
+
577
656
  edge_traces.append(
578
657
  {
579
658
  "type": "scatter",
@@ -581,11 +660,27 @@ class PlotlyFormatter:
581
660
  "y": [y0, y1, None],
582
661
  "mode": "lines",
583
662
  "line": {"width": 1.5, "color": "#888"},
584
- "hoverinfo": "none",
663
+ "hoverinfo": "text" if file_path else "none",
664
+ "hovertext": file_path if file_path else None,
585
665
  "showlegend": False,
586
666
  }
587
667
  )
588
668
 
669
+ # Add annotation at midpoint of edge if we have file path info
670
+ if file_name:
671
+ mid_x = (x0 + x1) / 2
672
+ mid_y = (y0 + y1) / 2
673
+ edge_annotations.append(
674
+ {
675
+ "x": mid_x,
676
+ "y": mid_y,
677
+ "text": file_name,
678
+ "showarrow": False,
679
+ "font": {"size": 9, "color": "#666"},
680
+ "bgcolor": "rgba(255,255,255,0.8)",
681
+ }
682
+ )
683
+
589
684
  # Build node trace
590
685
  node_x = [positions[n][0] for n in node_list if n in positions]
591
686
  node_y = [positions[n][1] for n in node_list if n in positions]
@@ -597,9 +692,10 @@ class PlotlyFormatter:
597
692
  "mode": "markers+text",
598
693
  "text": node_list,
599
694
  "textposition": "top center",
695
+ "textfont": {"size": 11},
600
696
  "hoverinfo": "text",
601
697
  "marker": {
602
- "size": 20,
698
+ "size": 15,
603
699
  "color": node_colors,
604
700
  "line": {"width": 2, "color": "#333"},
605
701
  },
@@ -637,22 +733,32 @@ class PlotlyFormatter:
637
733
  },
638
734
  ]
639
735
 
640
- direction_label = (
641
- "Upstream" if result.direction == "upstream" else "Downstream"
642
- )
736
+ direction_label = "Upstream" if result.direction == "upstream" else "Downstream"
643
737
  title = f"{direction_label} Lineage: {result.query_column}"
644
738
 
739
+ # Calculate figure dimensions based on graph size
740
+ min_height = 400
741
+ height_per_node = 50
742
+ calculated_height = max(min_height, len(node_list) * height_per_node)
743
+
744
+ layout: dict[str, Any] = {
745
+ "title": {"text": title},
746
+ "showlegend": True,
747
+ "legend": {"x": 1, "y": 1, "xanchor": "right"},
748
+ "hovermode": "closest",
749
+ "xaxis": {"visible": False},
750
+ "yaxis": {"visible": False},
751
+ "height": calculated_height,
752
+ "margin": {"l": 50, "r": 50, "t": 60, "b": 40},
753
+ }
754
+
755
+ # Add edge annotations if we have file path info
756
+ if edge_annotations:
757
+ layout["annotations"] = edge_annotations
758
+
645
759
  figure = {
646
760
  "data": edge_traces + [node_trace] + legend_traces,
647
- "layout": {
648
- "title": {"text": title},
649
- "showlegend": True,
650
- "legend": {"x": 1, "y": 1, "xanchor": "right"},
651
- "hovermode": "closest",
652
- "xaxis": {"visible": False},
653
- "yaxis": {"visible": False},
654
- "margin": {"l": 40, "r": 40, "t": 60, "b": 40},
655
- },
761
+ "layout": layout,
656
762
  }
657
763
 
658
764
  return json.dumps(figure, indent=2)