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.
- {sql_glider-0.1.24.dist-info → sql_glider-0.1.25.dist-info}/METADATA +1 -1
- {sql_glider-0.1.24.dist-info → sql_glider-0.1.25.dist-info}/RECORD +8 -8
- sqlglider/_version.py +2 -2
- sqlglider/cli.py +1 -1
- sqlglider/graph/diagram_formatters.py +106 -26
- {sql_glider-0.1.24.dist-info → sql_glider-0.1.25.dist-info}/WHEEL +0 -0
- {sql_glider-0.1.24.dist-info → sql_glider-0.1.25.dist-info}/entry_points.txt +0 -0
- {sql_glider-0.1.24.dist-info → sql_glider-0.1.25.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-glider
|
|
3
|
-
Version: 0.1.
|
|
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=
|
|
3
|
-
sqlglider/cli.py,sha256=
|
|
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
|
|
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.
|
|
36
|
-
sql_glider-0.1.
|
|
37
|
-
sql_glider-0.1.
|
|
38
|
-
sql_glider-0.1.
|
|
39
|
-
sql_glider-0.1.
|
|
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
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) *
|
|
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
|
|
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": "
|
|
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":
|
|
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
|
-
"
|
|
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(
|
|
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":
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|