sql-glider 0.1.24__py3-none-any.whl → 0.1.25__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.25
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=VIORluFSyo8DggJNI3m2ltXngK-bmCHX8hSwlGrwopY,706
3
+ sqlglider/cli.py,sha256=h0dAx-Dz_LkSfrWlxD8wU_G2WzWqzn14EjHAwajWVyQ,69591
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=-7BFG3z4EZrmiwnOAco-R-Axs5OL57ytMXGHuTh0z68,25321
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.25.dist-info/METADATA,sha256=qCGtZ5ROBNQbi-9NDmWUNdf9WtIG9NQoEVrplFzXsxs,31197
36
+ sql_glider-0.1.25.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
+ sql_glider-0.1.25.dist-info/entry_points.txt,sha256=HDuakHqHS5C0HFKsMIxMYmDU7-BLBGrnIJcYaVRu-s0,251
38
+ sql_glider-0.1.25.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
39
+ sql_glider-0.1.25.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.25'
32
+ __version_tuple__ = version_tuple = (0, 1, 25)
33
33
 
34
34
  __commit_id__ = commit_id = None
sqlglider/cli.py CHANGED
@@ -1727,7 +1727,7 @@ def graph_query(
1727
1727
  elif output_format == "plotly":
1728
1728
  from sqlglider.graph.diagram_formatters import PlotlyFormatter
1729
1729
 
1730
- print(PlotlyFormatter.format_query_result(result))
1730
+ print(PlotlyFormatter.format_query_result(result, graph=querier.graph))
1731
1731
 
1732
1732
  except ImportError as e:
1733
1733
  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
@@ -335,6 +335,8 @@ class DotFormatter:
335
335
  def _compute_layered_layout(
336
336
  nodes: list[str],
337
337
  edges: list[tuple[str, str]],
338
+ x_spacing: float = 250.0,
339
+ y_spacing: float = 100.0,
338
340
  ) -> dict[str, tuple[float, float]]:
339
341
  """Compute layered layout positions for nodes using topological ordering.
340
342
 
@@ -345,6 +347,8 @@ def _compute_layered_layout(
345
347
  Args:
346
348
  nodes: List of node identifiers
347
349
  edges: List of (source, target) edge tuples
350
+ x_spacing: Horizontal spacing between layers (default: 250 pixels)
351
+ y_spacing: Vertical spacing between nodes in the same layer (default: 100 pixels)
348
352
 
349
353
  Returns:
350
354
  Dictionary mapping node identifiers to (x, y) positions
@@ -395,15 +399,13 @@ def _compute_layered_layout(
395
399
 
396
400
  # Compute positions: x based on layer, y spread vertically within layer
397
401
  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
402
 
401
403
  for layer, layer_nodes in layer_groups.items():
402
404
  x = layer * x_spacing
403
405
  n = len(layer_nodes)
404
406
  for i, node in enumerate(sorted(layer_nodes)):
405
- # Center nodes vertically, spread them out
406
- y = (i - (n - 1) / 2) * 0.5
407
+ # Center nodes vertically, spread them out uniformly
408
+ y = (i - (n - 1) / 2) * y_spacing
407
409
  positions[node] = (x, y)
408
410
 
409
411
  return positions
@@ -444,6 +446,11 @@ class PlotlyFormatter:
444
446
  node_ids = [n.identifier for n in graph.nodes]
445
447
  edge_tuples = [(e.source_node, e.target_node) for e in graph.edges]
446
448
 
449
+ # Build lookup for edge file paths
450
+ edge_file_paths: dict[tuple[str, str], str] = {}
451
+ for e in graph.edges:
452
+ edge_file_paths[(e.source_node, e.target_node)] = e.file_path
453
+
447
454
  if not node_ids:
448
455
  # Empty graph
449
456
  figure: dict[str, Any] = {
@@ -459,24 +466,46 @@ class PlotlyFormatter:
459
466
 
460
467
  positions = _compute_layered_layout(node_ids, edge_tuples)
461
468
 
462
- # Build edge traces (one trace per edge for simplicity)
469
+ # Build edge traces with hover text for file path
463
470
  edge_traces: list[dict[str, Any]] = []
471
+ edge_annotations: list[dict[str, Any]] = []
464
472
  for src, tgt in edge_tuples:
465
473
  if src in positions and tgt in positions:
466
474
  x0, y0 = positions[src]
467
475
  x1, y1 = positions[tgt]
476
+ file_path = edge_file_paths.get((src, tgt), "")
477
+ # Extract just the filename for display
478
+ file_name = (
479
+ file_path.split("/")[-1].split("\\")[-1] if file_path else ""
480
+ )
481
+
468
482
  edge_traces.append(
469
483
  {
470
484
  "type": "scatter",
471
485
  "x": [x0, x1, None],
472
486
  "y": [y0, y1, None],
473
487
  "mode": "lines",
474
- "line": {"width": 1, "color": "#888"},
475
- "hoverinfo": "none",
488
+ "line": {"width": 1.5, "color": "#888"},
489
+ "hoverinfo": "text",
490
+ "hovertext": file_path,
476
491
  "showlegend": False,
477
492
  }
478
493
  )
479
494
 
495
+ # Add annotation at midpoint of edge
496
+ mid_x = (x0 + x1) / 2
497
+ mid_y = (y0 + y1) / 2
498
+ edge_annotations.append(
499
+ {
500
+ "x": mid_x,
501
+ "y": mid_y,
502
+ "text": file_name,
503
+ "showarrow": False,
504
+ "font": {"size": 9, "color": "#666"},
505
+ "bgcolor": "rgba(255,255,255,0.8)",
506
+ }
507
+ )
508
+
480
509
  # Build node trace
481
510
  node_x = [positions[n][0] for n in node_ids if n in positions]
482
511
  node_y = [positions[n][1] for n in node_ids if n in positions]
@@ -489,15 +518,21 @@ class PlotlyFormatter:
489
518
  "mode": "markers+text",
490
519
  "text": node_text,
491
520
  "textposition": "top center",
521
+ "textfont": {"size": 11},
492
522
  "hoverinfo": "text",
493
523
  "marker": {
494
- "size": 20,
524
+ "size": 15,
495
525
  "color": "#6495ED",
496
526
  "line": {"width": 2, "color": "#4169E1"},
497
527
  },
498
528
  "showlegend": False,
499
529
  }
500
530
 
531
+ # Calculate figure dimensions based on graph size
532
+ min_height = 400
533
+ height_per_node = 50
534
+ calculated_height = max(min_height, len(node_ids) * height_per_node)
535
+
501
536
  figure = {
502
537
  "data": edge_traces + [node_trace],
503
538
  "layout": {
@@ -506,14 +541,19 @@ class PlotlyFormatter:
506
541
  "hovermode": "closest",
507
542
  "xaxis": {"visible": False},
508
543
  "yaxis": {"visible": False},
509
- "margin": {"l": 40, "r": 40, "t": 60, "b": 40},
544
+ "height": calculated_height,
545
+ "margin": {"l": 50, "r": 50, "t": 60, "b": 40},
546
+ "annotations": edge_annotations,
510
547
  },
511
548
  }
512
549
 
513
550
  return json.dumps(figure, indent=2)
514
551
 
515
552
  @staticmethod
516
- def format_query_result(result: LineageQueryResult) -> str:
553
+ def format_query_result(
554
+ result: LineageQueryResult,
555
+ graph: Optional[LineageGraph] = None,
556
+ ) -> str:
517
557
  """Format query result as a Plotly JSON figure with styling.
518
558
 
519
559
  The queried column is highlighted in amber, root nodes in teal,
@@ -521,6 +561,7 @@ class PlotlyFormatter:
521
561
 
522
562
  Args:
523
563
  result: LineageQueryResult from upstream/downstream query
564
+ graph: Optional LineageGraph for edge file path labels
524
565
 
525
566
  Returns:
526
567
  JSON string representing a Plotly figure specification
@@ -530,6 +571,12 @@ class PlotlyFormatter:
530
571
  all_nodes = _collect_query_nodes(result)
531
572
  edges = _collect_query_edges(result)
532
573
 
574
+ # Build edge file path lookup if graph is provided
575
+ edge_file_paths: dict[tuple[str, str], str] = {}
576
+ if graph:
577
+ for e in graph.edges:
578
+ edge_file_paths[(e.source_node, e.target_node)] = e.file_path
579
+
533
580
  if not all_nodes:
534
581
  # Should not happen, but handle gracefully
535
582
  figure: dict[str, Any] = {
@@ -568,12 +615,18 @@ class PlotlyFormatter:
568
615
  else:
569
616
  node_colors.append("#6495ED") # Default blue
570
617
 
571
- # Build edge traces
618
+ # Build edge traces with optional file path labels
572
619
  edge_traces: list[dict[str, Any]] = []
620
+ edge_annotations: list[dict[str, Any]] = []
573
621
  for src, tgt in sorted(edges):
574
622
  if src in positions and tgt in positions:
575
623
  x0, y0 = positions[src]
576
624
  x1, y1 = positions[tgt]
625
+ file_path = edge_file_paths.get((src, tgt), "")
626
+ file_name = (
627
+ file_path.split("/")[-1].split("\\")[-1] if file_path else ""
628
+ )
629
+
577
630
  edge_traces.append(
578
631
  {
579
632
  "type": "scatter",
@@ -581,11 +634,27 @@ class PlotlyFormatter:
581
634
  "y": [y0, y1, None],
582
635
  "mode": "lines",
583
636
  "line": {"width": 1.5, "color": "#888"},
584
- "hoverinfo": "none",
637
+ "hoverinfo": "text" if file_path else "none",
638
+ "hovertext": file_path if file_path else None,
585
639
  "showlegend": False,
586
640
  }
587
641
  )
588
642
 
643
+ # Add annotation at midpoint of edge if we have file path info
644
+ if file_name:
645
+ mid_x = (x0 + x1) / 2
646
+ mid_y = (y0 + y1) / 2
647
+ edge_annotations.append(
648
+ {
649
+ "x": mid_x,
650
+ "y": mid_y,
651
+ "text": file_name,
652
+ "showarrow": False,
653
+ "font": {"size": 9, "color": "#666"},
654
+ "bgcolor": "rgba(255,255,255,0.8)",
655
+ }
656
+ )
657
+
589
658
  # Build node trace
590
659
  node_x = [positions[n][0] for n in node_list if n in positions]
591
660
  node_y = [positions[n][1] for n in node_list if n in positions]
@@ -597,9 +666,10 @@ class PlotlyFormatter:
597
666
  "mode": "markers+text",
598
667
  "text": node_list,
599
668
  "textposition": "top center",
669
+ "textfont": {"size": 11},
600
670
  "hoverinfo": "text",
601
671
  "marker": {
602
- "size": 20,
672
+ "size": 15,
603
673
  "color": node_colors,
604
674
  "line": {"width": 2, "color": "#333"},
605
675
  },
@@ -637,22 +707,32 @@ class PlotlyFormatter:
637
707
  },
638
708
  ]
639
709
 
640
- direction_label = (
641
- "Upstream" if result.direction == "upstream" else "Downstream"
642
- )
710
+ direction_label = "Upstream" if result.direction == "upstream" else "Downstream"
643
711
  title = f"{direction_label} Lineage: {result.query_column}"
644
712
 
713
+ # Calculate figure dimensions based on graph size
714
+ min_height = 400
715
+ height_per_node = 50
716
+ calculated_height = max(min_height, len(node_list) * height_per_node)
717
+
718
+ layout: dict[str, Any] = {
719
+ "title": {"text": title},
720
+ "showlegend": True,
721
+ "legend": {"x": 1, "y": 1, "xanchor": "right"},
722
+ "hovermode": "closest",
723
+ "xaxis": {"visible": False},
724
+ "yaxis": {"visible": False},
725
+ "height": calculated_height,
726
+ "margin": {"l": 50, "r": 50, "t": 60, "b": 40},
727
+ }
728
+
729
+ # Add edge annotations if we have file path info
730
+ if edge_annotations:
731
+ layout["annotations"] = edge_annotations
732
+
645
733
  figure = {
646
734
  "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
- },
735
+ "layout": layout,
656
736
  }
657
737
 
658
738
  return json.dumps(figure, indent=2)