ripple-sql 0.1.4__tar.gz → 0.1.5__tar.gz
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.
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/CHANGELOG.md +7 -1
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/PKG-INFO +1 -1
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/pyproject.toml +1 -1
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/home.py +17 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/static/answer.css +4 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/static/answer.html +1 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/static/explore.js +27 -13
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/static/focus.js +1 -14
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/.gitignore +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/LICENSE +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/README.md +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/__init__.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/answer.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/answer_page.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/cache.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/ci.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/ci_signature.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/cli.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/doctor.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/__init__.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/budget.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/column_lineage.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/column_ref.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/cte_tracing.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/dependencies.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/dialect.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/dispatch.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/extraction.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/generators.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/jinja.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/json_sources.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/macro_source.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/pipeline.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/preprocess.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/safe_gen.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/schema_qualification.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/scope.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/select_sources.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/sql_script.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/statement.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/tech_debt.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/tsql_catalog.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/tsql_scalar_vars.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/tsql_tvf.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/tsql_xml.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/types.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/unused_deps.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/engine/validation.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/graph.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/__init__.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/dbt.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/dbt_config.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/identity.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/sidecar.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/sqldir.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/loaders/types.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/lookml.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/mcp_server.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/names.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/project.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/py.typed +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/render.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/render_shims.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/schemas.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/semantic.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/server.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/sourcefiles.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/star_resolution.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/static/answer_twin.js +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/static/find.js +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/usage/__init__.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/usage/cli.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/usage/collect.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/usage/discover.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/usage/ingest.py +0 -0
- {ripple_sql-0.1.4 → ripple_sql-0.1.5}/src/ripple/usage/report.py +0 -0
|
@@ -6,6 +6,11 @@ Notable changes to Ripple. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.5] - 2026-09-09
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Every column under a model on the page carries the number of models that read it directly, the same number a model chip carries, so the cost of a change shows before the question is asked. A column no link touches (a constant, or one the engine could not trace) is drawn loose, dashed and faint, with "no link found" in the key; r/dataengineering asked for "a clear view of mapped vs unmapped fields (with some kind of legend)", and a traced-looking constant would have been a lie.
|
|
13
|
+
|
|
9
14
|
## [0.1.4] - 2026-09-09
|
|
10
15
|
|
|
11
16
|
### Changed
|
|
@@ -249,7 +254,8 @@ First public release.
|
|
|
249
254
|
### Removed
|
|
250
255
|
- The dark whole-graph canvas that `ripple serve` used to open (`static/index.html`). Every surface now draws one answer.
|
|
251
256
|
|
|
252
|
-
[Unreleased]: https://github.com/bteh/ripple/compare/v0.1.
|
|
257
|
+
[Unreleased]: https://github.com/bteh/ripple/compare/v0.1.5...HEAD
|
|
258
|
+
[0.1.5]: https://github.com/bteh/ripple/releases/tag/v0.1.5
|
|
253
259
|
[0.1.4]: https://github.com/bteh/ripple/releases/tag/v0.1.4
|
|
254
260
|
[0.1.3]: https://github.com/bteh/ripple/releases/tag/v0.1.3
|
|
255
261
|
[0.1.2]: https://github.com/bteh/ripple/releases/tag/v0.1.2
|
|
@@ -130,10 +130,19 @@ def model_map(nodes: list[dict], edges) -> dict:
|
|
|
130
130
|
}
|
|
131
131
|
fed = {e.dst_model for e in edges}
|
|
132
132
|
seen: dict[str, dict[str, None]] = {}
|
|
133
|
+
readers: dict[tuple[str, str], set[str]] = {}
|
|
134
|
+
linked: set[tuple[str, str]] = set()
|
|
133
135
|
for e in edges:
|
|
134
136
|
for model, column in ((e.src_model, e.src_column), (e.dst_model, e.dst_column)):
|
|
135
137
|
if _is_column(column):
|
|
136
138
|
seen.setdefault(model, {})[column] = None
|
|
139
|
+
if e.kind != "value" or not (_is_column(e.src_column) and _is_column(e.dst_column)):
|
|
140
|
+
continue
|
|
141
|
+
linked.add((e.src_model, e.src_column))
|
|
142
|
+
linked.add((e.dst_model, e.dst_column))
|
|
143
|
+
# a dashboard number is not a reader; only models count (as in starters)
|
|
144
|
+
if not e.dst_model.startswith(DASHBOARD_PREFIXES):
|
|
145
|
+
readers.setdefault((e.src_model, e.src_column), set()).add(e.dst_model)
|
|
137
146
|
for name in sorted({e.src_model for e in edges} | fed):
|
|
138
147
|
if name in members:
|
|
139
148
|
known = members[name]["columns"]
|
|
@@ -151,6 +160,14 @@ def model_map(nodes: list[dict], edges) -> dict:
|
|
|
151
160
|
"status": None,
|
|
152
161
|
"path": None,
|
|
153
162
|
}
|
|
163
|
+
for m in members.values():
|
|
164
|
+
# per column, aligned with `columns`: how many models read it directly;
|
|
165
|
+
# -1 when no link touches it at all (a constant, or one the engine
|
|
166
|
+
# could not trace), so the page never draws such a column as traced
|
|
167
|
+
m["reads"] = [
|
|
168
|
+
len(readers.get((m["id"], c), ())) if (m["id"], c) in linked else -1
|
|
169
|
+
for c in m["columns"]
|
|
170
|
+
]
|
|
154
171
|
links = _links(edges)
|
|
155
172
|
layers = _layers(members, links)
|
|
156
173
|
models = [{**m, "layer": layers[m["id"]]} for m in members.values()]
|
|
@@ -77,6 +77,7 @@ h1 code{font:500 24px/1 var(--mono); letter-spacing:0}
|
|
|
77
77
|
.key i.r{border-top-style:dashed; border-color:var(--review)}
|
|
78
78
|
.key s{display:inline-block; width:11px; height:11px; border-radius:2px; background:var(--ink); border:1px solid var(--ink); text-decoration:none}
|
|
79
79
|
.key s.src{background:var(--well); border:1px dashed var(--line-strong)}
|
|
80
|
+
.key s.unk{background:transparent; border:1px dashed var(--faint)}
|
|
80
81
|
.key small.n{font:400 11.5px/1 var(--sans); color:var(--faint); border:1px solid var(--line-strong); border-radius:var(--r); padding:2px 5px; background:var(--surface)}
|
|
81
82
|
.hint{margin-left:auto; font-size:12.5px; color:var(--faint)}
|
|
82
83
|
.rows{padding:0 22px 14px; font-size:13.5px; color:var(--muted)}
|
|
@@ -110,6 +111,9 @@ pre{margin:0; padding:16px 22px 18px; overflow-x:auto; font:400 13px/1.65 var(--
|
|
|
110
111
|
.cols{display:flex; flex-direction:column; gap:5px; margin:0 0 6px 10px; padding-left:10px; border-left:1px solid var(--line)}
|
|
111
112
|
.cols .gname{max-width:36ch; white-space:normal; line-height:1.45; padding-top:2px}
|
|
112
113
|
.cols .node{cursor:pointer}
|
|
114
|
+
.cols .node small{font:400 11px/1 var(--sans); color:var(--faint); margin-left:5px}
|
|
115
|
+
.cols .node.unk{color:var(--muted)}
|
|
116
|
+
.cols .node.unk:hover{border-color:var(--accent)}
|
|
113
117
|
.cols .node:hover{border-color:var(--accent); color:var(--accent-strong)}
|
|
114
118
|
.more-chips{font:500 12.5px var(--sans); color:var(--accent); background:none; border:0; padding:4px 0; cursor:pointer; text-align:left}
|
|
115
119
|
.more-chips:hover{text-decoration:underline}
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
<span class="key"><i class="r"></i> needs review</span>
|
|
36
36
|
<span class="key" id="key-chain" hidden><s class="up"></s> feeds it <s class="sel"></s> chosen <s class="down"></s> reads it</span>
|
|
37
37
|
<span class="key"><small class="n">3</small> models that read it</span>
|
|
38
|
+
<span class="key"><s class="unk"></s> no link found</span>
|
|
38
39
|
<span class="hidden-note" id="maphidden" hidden></span>
|
|
39
40
|
<span class="hint" id="maphint">Hover lights the chain. Click a model for its columns, a column to ask.</span>
|
|
40
41
|
</div>
|
|
@@ -8,6 +8,32 @@
|
|
|
8
8
|
// nothing here fetches.
|
|
9
9
|
var COMPACT_AT = 40, MAP_COLUMNS = 60, INDEX_PEEK = 6, INDEX_UNFOLD = 60, INDEX_GROUPS = 24;
|
|
10
10
|
|
|
11
|
+
// a model's columns as chips: each with the number of models that read it
|
|
12
|
+
// directly, and a column no link touches (a constant, or one the engine could
|
|
13
|
+
// not trace) drawn loose so nobody reads it as traced. Used under a model on
|
|
14
|
+
// the small map and under the chosen model in focus.
|
|
15
|
+
function columnChips(m, opts){
|
|
16
|
+
var esc = opts.esc, cap = opts.cap || MAP_COLUMNS, cols = document.createElement('div');
|
|
17
|
+
cols.className = 'cols';
|
|
18
|
+
m.columns.slice(0, cap).forEach(function(c, i){
|
|
19
|
+
// reads[i] is the model count, or -1 when no link touches the column
|
|
20
|
+
var n = m.reads ? m.reads[i] : 0, loose = n < 0, s = document.createElement('button');
|
|
21
|
+
s.type = 'button'; s.className = 'node in' + (loose ? ' unk' : '');
|
|
22
|
+
s.innerHTML = esc(c) + (n > 0 ? ' <small>' + n + '</small>' : '');
|
|
23
|
+
s.title = (loose ? 'no link found: a constant, or one Ripple could not trace' :
|
|
24
|
+
n ? 'read by ' + n + ' model' + (n === 1 ? '' : 's') + ' directly' : 'nothing reads it directly') + ' · click to ask about ' + m.id + '.' + c;
|
|
25
|
+
s.addEventListener('click', function(){ opts.onAsk(m.id + '.' + c); });
|
|
26
|
+
cols.appendChild(s);
|
|
27
|
+
});
|
|
28
|
+
var note = null;
|
|
29
|
+
if (m.columns.length > cap) note = '+ ' + (m.columns.length - cap) + ' more, ask by name';
|
|
30
|
+
else if (!m.declared && m.kind === 'source') note = (m.columns.length ? 'only the columns this project names; the full list is' : 'no column list; it is') +
|
|
31
|
+
' not declared in the project (a select * reads it unnamed). Coverage below adds it.';
|
|
32
|
+
else if (!m.columns.length) note = 'no columns known';
|
|
33
|
+
if (note){ var g = document.createElement('span'); g.className = 'gname'; g.textContent = note; cols.appendChild(g); }
|
|
34
|
+
return cols;
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
function renderMap(root, map, opts){
|
|
12
38
|
var inner = root.querySelector('.board-inner'), svg = root.querySelector('.wires'), head = root.querySelector('.map-head');
|
|
13
39
|
var esc = opts.esc, compact = map.models.length > COMPACT_AT;
|
|
@@ -77,19 +103,7 @@ function renderMap(root, map, opts){
|
|
|
77
103
|
if (pinned === m.id){ pinned = null; unlight(); }
|
|
78
104
|
draw(); return;
|
|
79
105
|
}
|
|
80
|
-
var cols =
|
|
81
|
-
m.columns.slice(0, MAP_COLUMNS).forEach(function(c){
|
|
82
|
-
var s = el('button', 'node in'); s.type = 'button'; s.textContent = c;
|
|
83
|
-
s.title = 'ask about ' + m.id + '.' + c;
|
|
84
|
-
s.addEventListener('click', function(){ opts.onAsk(m.id + '.' + c); });
|
|
85
|
-
cols.appendChild(s);
|
|
86
|
-
});
|
|
87
|
-
var note = null;
|
|
88
|
-
if (m.columns.length > MAP_COLUMNS) note = '+ ' + (m.columns.length - MAP_COLUMNS) + ' more, ask by name';
|
|
89
|
-
else if (!m.declared && m.kind === 'source') note = (m.columns.length ? 'only the columns this project names; the full list is' : 'no column list; it is') +
|
|
90
|
-
' not declared in the project (a select * reads it unnamed). Coverage below adds it.';
|
|
91
|
-
else if (!m.columns.length) note = 'no columns known';
|
|
92
|
-
if (note){ var n = el('span', 'gname'); n.textContent = note; cols.appendChild(n); }
|
|
106
|
+
var cols = columnChips(m, {esc: esc, onAsk: opts.onAsk, cap: MAP_COLUMNS});
|
|
93
107
|
g.appendChild(cols); g.classList.add('open'); b.setAttribute('aria-expanded', 'true');
|
|
94
108
|
// a click keeps the chain lit so the mouse can travel to the columns
|
|
95
109
|
if (pinned && pinned !== m.id){ var was = groups[pinned].querySelector('.cols'); if (was){ was.remove(); groups[pinned].classList.remove('open'); chips[pinned].setAttribute('aria-expanded', 'false'); } }
|
|
@@ -81,20 +81,7 @@ function renderFocus(inner, svg, map, idx, id, opts){
|
|
|
81
81
|
b.classList.add('sel', 'anchor'); b.setAttribute('aria-current', 'true');
|
|
82
82
|
dist[id] = 0;
|
|
83
83
|
g.appendChild(b);
|
|
84
|
-
|
|
85
|
-
m.columns.slice(0, FOCUS_COLUMNS).forEach(function(c){
|
|
86
|
-
var s = el('button', 'node in'); s.type = 'button'; s.textContent = c;
|
|
87
|
-
s.title = 'ask about ' + m.id + '.' + c;
|
|
88
|
-
s.addEventListener('click', function(){ opts.onAsk(m.id + '.' + c); });
|
|
89
|
-
cols.appendChild(s);
|
|
90
|
-
});
|
|
91
|
-
var note = null;
|
|
92
|
-
if (m.columns.length > FOCUS_COLUMNS) note = '+ ' + (m.columns.length - FOCUS_COLUMNS) + ' more, ask by name';
|
|
93
|
-
else if (!m.declared && m.kind === 'source') note = (m.columns.length ? 'only the columns this project names; the full list is' : 'no column list; it is') +
|
|
94
|
-
' not declared in the project (a select * reads it unnamed). Coverage below adds it.';
|
|
95
|
-
else if (!m.columns.length) note = 'no columns known';
|
|
96
|
-
if (note){ var n = el('span', 'gname'); n.textContent = note; cols.appendChild(n); }
|
|
97
|
-
g.appendChild(cols); lvl.appendChild(g);
|
|
84
|
+
g.appendChild(columnChips(m, {esc: esc, onAsk: opts.onAsk, cap: FOCUS_COLUMNS})); lvl.appendChild(g);
|
|
98
85
|
return lvl;
|
|
99
86
|
}
|
|
100
87
|
function render(){
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|