sql-glider 0.1.25__py3-none-any.whl → 0.1.27__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.25.dist-info → sql_glider-0.1.27.dist-info}/METADATA +2 -1
- {sql_glider-0.1.25.dist-info → sql_glider-0.1.27.dist-info}/RECORD +8 -8
- sqlglider/_version.py +2 -2
- sqlglider/cli.py +6 -2
- sqlglider/graph/diagram_formatters.py +123 -10
- {sql_glider-0.1.25.dist-info → sql_glider-0.1.27.dist-info}/WHEEL +0 -0
- {sql_glider-0.1.25.dist-info → sql_glider-0.1.27.dist-info}/entry_points.txt +0 -0
- {sql_glider-0.1.25.dist-info → sql_glider-0.1.27.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.27
|
|
4
4
|
Summary: SQL Utility Toolkit for better understanding, use, and governance of your queries in a native environment.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rycowhi/sql-glider/
|
|
6
6
|
Project-URL: Repository, https://github.com/rycowhi/sql-glider/
|
|
@@ -30,6 +30,7 @@ Provides-Extra: databricks
|
|
|
30
30
|
Requires-Dist: databricks-sdk>=0.20.0; extra == 'databricks'
|
|
31
31
|
Provides-Extra: plotly
|
|
32
32
|
Requires-Dist: plotly>=5.0.0; extra == 'plotly'
|
|
33
|
+
Requires-Dist: pygraphviz>=1.11; extra == 'plotly'
|
|
33
34
|
Description-Content-Type: text/markdown
|
|
34
35
|
|
|
35
36
|
# SQL Glider
|
|
@@ -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=o2dyLbB_Uhc2yY2R7iheES_lRnDBGV9Hc4iNgiJ_XTo,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
|
|
15
|
+
sqlglider/graph/diagram_formatters.py,sha256=Qibe8GSnDyco5YqAms0nRXxuveA1PnWwHa8MV5FgvOA,29328
|
|
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.27.dist-info/METADATA,sha256=rq45QV36gpouCXs42p2-kgpArng9wSmX2EuNT2tVRTs,31248
|
|
36
|
+
sql_glider-0.1.27.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
37
|
+
sql_glider-0.1.27.dist-info/entry_points.txt,sha256=HDuakHqHS5C0HFKsMIxMYmDU7-BLBGrnIJcYaVRu-s0,251
|
|
38
|
+
sql_glider-0.1.27.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
39
|
+
sql_glider-0.1.27.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.27'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 27)
|
|
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(
|
|
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
|
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
145
|
-
|
|
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(
|
|
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
|
|
|
@@ -338,7 +364,11 @@ def _compute_layered_layout(
|
|
|
338
364
|
x_spacing: float = 250.0,
|
|
339
365
|
y_spacing: float = 100.0,
|
|
340
366
|
) -> dict[str, tuple[float, float]]:
|
|
341
|
-
"""Compute layered layout positions for nodes using
|
|
367
|
+
"""Compute layered layout positions for nodes using Graphviz's dot algorithm.
|
|
368
|
+
|
|
369
|
+
Uses Graphviz's dot layout engine (Sugiyama algorithm) which minimizes edge
|
|
370
|
+
crossings by using the barycenter heuristic to optimize node ordering within
|
|
371
|
+
each layer.
|
|
342
372
|
|
|
343
373
|
Positions nodes in layers from left to right based on their dependencies.
|
|
344
374
|
Nodes with no incoming edges are placed in layer 0, their dependents in
|
|
@@ -356,6 +386,89 @@ def _compute_layered_layout(
|
|
|
356
386
|
if not nodes:
|
|
357
387
|
return {}
|
|
358
388
|
|
|
389
|
+
try:
|
|
390
|
+
import pygraphviz # noqa: F401 # type: ignore[import-not-found]
|
|
391
|
+
|
|
392
|
+
return _compute_graphviz_layout(nodes, edges, x_spacing, y_spacing)
|
|
393
|
+
except ImportError:
|
|
394
|
+
# Fallback to simple layered layout if pygraphviz not available
|
|
395
|
+
return _compute_simple_layered_layout(nodes, edges, x_spacing, y_spacing)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _compute_graphviz_layout(
|
|
399
|
+
nodes: list[str],
|
|
400
|
+
edges: list[tuple[str, str]],
|
|
401
|
+
x_spacing: float = 250.0,
|
|
402
|
+
y_spacing: float = 100.0,
|
|
403
|
+
) -> dict[str, tuple[float, float]]:
|
|
404
|
+
"""Compute layout using Graphviz's dot algorithm with crossing minimization.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
nodes: List of node identifiers
|
|
408
|
+
edges: List of (source, target) edge tuples
|
|
409
|
+
x_spacing: Horizontal spacing between layers
|
|
410
|
+
y_spacing: Vertical spacing between nodes in the same layer
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Dictionary mapping node identifiers to (x, y) positions
|
|
414
|
+
"""
|
|
415
|
+
import pygraphviz as pgv # type: ignore[import-not-found]
|
|
416
|
+
|
|
417
|
+
# Create directed graph
|
|
418
|
+
g = pgv.AGraph(directed=True, rankdir="LR")
|
|
419
|
+
|
|
420
|
+
# Set graph attributes for spacing
|
|
421
|
+
g.graph_attr["ranksep"] = str(x_spacing / 72.0) # Convert pixels to inches
|
|
422
|
+
g.graph_attr["nodesep"] = str(y_spacing / 72.0)
|
|
423
|
+
|
|
424
|
+
# Add nodes
|
|
425
|
+
for node in nodes:
|
|
426
|
+
g.add_node(node)
|
|
427
|
+
|
|
428
|
+
# Add edges (only for nodes that exist in our node list)
|
|
429
|
+
node_set = set(nodes)
|
|
430
|
+
for src, tgt in edges:
|
|
431
|
+
if src in node_set and tgt in node_set:
|
|
432
|
+
g.add_edge(src, tgt)
|
|
433
|
+
|
|
434
|
+
# Compute layout using dot algorithm
|
|
435
|
+
g.layout(prog="dot")
|
|
436
|
+
|
|
437
|
+
# Extract positions
|
|
438
|
+
positions: dict[str, tuple[float, float]] = {}
|
|
439
|
+
for node in nodes:
|
|
440
|
+
n = g.get_node(node)
|
|
441
|
+
# Position is returned as "x,y" string in points (1/72 inch)
|
|
442
|
+
pos_str = n.attr.get("pos", "0,0")
|
|
443
|
+
if pos_str:
|
|
444
|
+
x_str, y_str = pos_str.split(",")
|
|
445
|
+
# Convert from points to our coordinate system
|
|
446
|
+
x = float(x_str)
|
|
447
|
+
y = float(y_str)
|
|
448
|
+
positions[node] = (x, y)
|
|
449
|
+
|
|
450
|
+
return positions
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _compute_simple_layered_layout(
|
|
454
|
+
nodes: list[str],
|
|
455
|
+
edges: list[tuple[str, str]],
|
|
456
|
+
x_spacing: float = 250.0,
|
|
457
|
+
y_spacing: float = 100.0,
|
|
458
|
+
) -> dict[str, tuple[float, float]]:
|
|
459
|
+
"""Fallback simple layered layout using topological ordering.
|
|
460
|
+
|
|
461
|
+
Used when pygraphviz is not available. Does not minimize edge crossings.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
nodes: List of node identifiers
|
|
465
|
+
edges: List of (source, target) edge tuples
|
|
466
|
+
x_spacing: Horizontal spacing between layers
|
|
467
|
+
y_spacing: Vertical spacing between nodes in the same layer
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Dictionary mapping node identifiers to (x, y) positions
|
|
471
|
+
"""
|
|
359
472
|
# Build adjacency structures
|
|
360
473
|
incoming: dict[str, set[str]] = defaultdict(set)
|
|
361
474
|
outgoing: dict[str, set[str]] = defaultdict(set)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|