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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-glider
3
- Version: 0.1.25
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=VIORluFSyo8DggJNI3m2ltXngK-bmCHX8hSwlGrwopY,706
3
- sqlglider/cli.py,sha256=h0dAx-Dz_LkSfrWlxD8wU_G2WzWqzn14EjHAwajWVyQ,69591
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=-7BFG3z4EZrmiwnOAco-R-Axs5OL57ytMXGHuTh0z68,25321
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.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,,
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.25'
32
- __version_tuple__ = version_tuple = (0, 1, 25)
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(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
 
@@ -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
 
@@ -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 topological ordering.
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)