From dec40f15037c77d5bcc3cf05de60e38ef3738875 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Tue, 24 Mar 2026 08:52:10 -0600 Subject: [PATCH 001/109] Stylized parallel coordinate plot --- examples/perf_analyzer/fly_server.py | 149 ++++++++++++++---------- examples/perf_analyzer/runner_script.sh | 6 +- 2 files changed, 91 insertions(+), 64 deletions(-) diff --git a/examples/perf_analyzer/fly_server.py b/examples/perf_analyzer/fly_server.py index e3465720..717b0452 100755 --- a/examples/perf_analyzer/fly_server.py +++ b/examples/perf_analyzer/fly_server.py @@ -353,16 +353,24 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) combined_all_df = merged_df[merged_df.cname != None].sort_values(by=['date'], ascending=True) combined_all_df["formatted_date"] = pd.to_datetime(combined_all_df['date']).dt.strftime("%b-%d,%Y(%H:%M:%S)") - combined_all_df = combined_all_df.dropna(subset=perf_filter.extend(["date"])).reset_index(drop=True) + combined_all_df = combined_all_df\ + .dropna(subset=perf_filter.extend(["date"])).reset_index(drop=True)\ + .drop_duplicates(subset=['formatted_date'], keep='first') + + # dims = [] + # labels = [] - # print(combined_all_df["formatted_date"]) + print(combined_all_df["formatted_date"]) + # print(combined_all_df["branch"]) parCordValues = list() - if sorted_df is not None and len(sorted_df) > 0: + if combined_all_df is not None and len(combined_all_df) > 0: f_col = combined_all_df.index.values + # labels.append("Commit
Time") + # dims.append(f_col) parCordValues.append( dict(range = [f_col.min(), f_col.max()], values = f_col, - label = "Commit
Time", tickvals = f_col, ticktext = combined_all_df["formatted_date"].astype(str)) + label = "
Commit
Time", tickvals = f_col, ticktext = combined_all_df["formatted_date"].astype(str)) ) for col_name in perf_filter:# ["pdv", "cell_advection", "mpi_halo_exchange", "self_halo_exchange", "momentum_advection", "total"]: if col_name == "date": @@ -371,7 +379,7 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) # Replace the second to last underscore with a white space in the label parts = col_name.split('_') csum = 0 - label = '' + label = '
' for part in parts: if csum > 0: label += '_' @@ -380,11 +388,13 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) if csum > len(col_name) // 2: label += "
" csum = 0 + # labels.append(label) + # dims.append(f_col) parCordValues.append( dict(range = [f_col.min(), f_col.max()], label = label, values = f_col) ) - + # if len(dims) == 0: if len(parCordValues) == 0: fig = go.Figure().add_annotation( x=2, y=2, @@ -395,10 +405,11 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) fig.update_layout( xaxis = { "visible": False }, yaxis = { "visible": False }) + # return fig else: fig = go.Figure(data=go.Parcoords( line = dict(color = parCordValues[0]['values'], - colorscale = 'Electric', + colorscale = [[0, 'blue'], [1, 'blue']], showscale = False, colorbar = dict(x=-0.15, showticklabels=False), cmin = parCordValues[0]['range'][0], @@ -406,9 +417,12 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) dimensions = parCordValues, labelangle = 0, labelside = "bottom", + labelfont = dict(size=16, color='black'), + rangefont = dict(size=14, color='black'), + tickfont = dict(size=12, color='black'), ) ) - fig.update_layout(margin=dict(l=150)) + fig.update_layout(margin=dict(l=160)) return fig # @callback( @@ -546,68 +560,75 @@ def main(perf_data, git_nodes): # 'textOverflow': 'ellipsis', } ), - ], style={'width': 790,'marginTop': 0,'marginLeft': 0}) + ], style={'width': '100%','marginTop': 0,'marginLeft': 0}), # html.Div( # html.Img(src='assets/image.svg', # style={'marginLeft': 15, 'marginRight': 15, 'marginTop': 30, 'width': 310}) # ) + # ], style={ + # 'width': '60%', + # 'height': '1000px', + # 'marginLeft': 10, + # 'marginTop': 5, + # 'marginRight': 10 + # }), + html.Div([ + html.Div([ + html.H4("Search any variable, (regex or plaintext):"), + dcc.Input(id='custom-var-search', value='pragma, define', type='text', style={'marginBottom': 10}), + html.Div(id='search-var-info', children='Choose files types to filter'), + dcc.Dropdown( + options={r"\.c":".c", r"\.cc": ".cc", r"\.py":".py", r"\.f90":".f90", r"\.ipynb": ".ipynb"}, + value = [r"\.c", r"\.cc",], + id="filter-file-multi-options", + clearable=True, + optionHeight=40, + multi=True + ), + html.Div(id='file-filter-selection-text', children='Filtered Files', style={'marginBottom': 10}), + html.Button('Search', id='submit-var-search', n_clicks=0, disabled=True), + dash_table.DataTable(id='var-search-table', + columns=[ + {'name': 'variable', 'id': 'var_name', 'type': 'text'}, + {'name': 'file name', 'id': 'file_name', 'type': 'text'}, + # {'name': 'occurance', 'id': 'occ', 'type': 'numeric'} + ], + filter_action='native', + editable=False, + sort_action="native", + row_selectable="multi", + row_deletable=True, + style_cell={'textAlign': 'left'}, + style_table={ + 'height': '250px', 'minHeight': '200px', 'maxHeight': '250px', + 'overflow': 'auto', + 'font-size': '12px', + }, + style_data={ + # 'width': '100px', 'minWidth': '100px', + 'maxWidth': '100px', + 'whiteSpace': 'normal', + 'overflow': 'auto', + # 'textOverflow': 'ellipsis', + } + ), + # html.Div(id='var-results-table', style={'marginTop': 10}), + html.Div(id='code-view', style={'marginTop': 10, + 'height': '1000px', 'maxHeight': '1000px', + 'overflow': 'auto', + }), + ], style={'marginLeft': 0, 'marginTop': 5}), + ], style={ + 'width': '100%', + 'marginLeft': 0, + }), ], style={ - 'width': '60%', + 'width': '100%', 'height': '1000px', 'marginLeft': 10, 'marginTop': 5, 'marginRight': 10 }), - html.Div([ - html.Div([ - html.H4("Search any variable, (regex or plaintext):"), - dcc.Input(id='custom-var-search', value='pragma, define', type='text', style={'marginBottom': 10}), - html.Div(id='search-var-info', children='Choose files types to filter'), - dcc.Dropdown( - options={r"\.c":".c", r"\.cc": ".cc", r"\.py":".py", r"\.f90":".f90", r"\.ipynb": ".ipynb"}, - value = [r"\.c", r"\.cc",], - id="filter-file-multi-options", - clearable=True, - optionHeight=40, - multi=True - ), - html.Div(id='file-filter-selection-text', children='Filtered Files', style={'marginBottom': 10}), - html.Button('Search', id='submit-var-search', n_clicks=0, disabled=True), - dash_table.DataTable(id='var-search-table', - columns=[ - {'name': 'variable', 'id': 'var_name', 'type': 'text'}, - {'name': 'file name', 'id': 'file_name', 'type': 'text'}, - # {'name': 'occurance', 'id': 'occ', 'type': 'numeric'} - ], - filter_action='native', - editable=False, - sort_action="native", - row_selectable="multi", - row_deletable=True, - style_cell={'textAlign': 'left'}, - style_table={ - 'height': '250px', 'minHeight': '200px', 'maxHeight': '250px', - 'overflow': 'auto', - 'font-size': '12px', - }, - style_data={ - # 'width': '100px', 'minWidth': '100px', - 'maxWidth': '100px', - 'whiteSpace': 'normal', - 'overflow': 'auto', - # 'textOverflow': 'ellipsis', - } - ), - # html.Div(id='var-results-table', style={'marginTop': 10}), - html.Div(id='code-view', style={'marginTop': 10, - 'height': '1000px', 'maxHeight': '1000px', - 'overflow': 'auto', - }), - ], style={'marginLeft': 0, 'marginTop': 5}), - ], style={ - 'width': '40%', - 'marginLeft': 0, - }), ], fluid=True, style={'display': 'flex'}, @@ -744,12 +765,18 @@ def action_on_selected_vars(rows, derived_virtual_selected_rows, selected_commit return mk_component elif len(derived_selected_commits_list) > 2: return dcc.Markdown("##### Please select upto two commits", id='actual-source-block') + elif len(derived_selected_commits_list) == 1: + return dcc.Markdown("##### Please select upto two commits", id='actual-source-block') if derived_virtual_selected_rows is not None and len(derived_virtual_selected_rows) > 0 and len(rows) >= derived_virtual_selected_rows[0]: mk_data = list() for i in range(len(derived_virtual_selected_rows)): mk_data.append(rows[derived_virtual_selected_rows[i]]['file_name']) + # if len(derived_selected_commits_list) > 0 and derived_selected_commits_list[0] not in selected_commits_row: + # return dcc.Markdown("##### Please select two commits to compare", id='actual-source-block') + # if len(derived_selected_commits_list) > 1 and derived_selected_commits_list[1] not in selected_commits_row: + # return dcc.Markdown("##### Please select two commits to compare", id='actual-source-block') git_repo = getGitRepo(perf_runner.git_user_repo) first_commit_hash = selected_commits_row[derived_selected_commits_list[0]]["long_hash"] second_commit_hash = selected_commits_row[derived_selected_commits_list[1]]["long_hash"] @@ -835,7 +862,7 @@ def find_interesting_perf_metric(df): if column == 'testname' or column.startswith("git_") or 'min' in column or 'max' in column or 'x_cells' in column or 'y_cells' in column: continue df[column] = pd.to_numeric(df[column].fillna(value=np.nan), errors='coerce') - another = df.select_dtypes(include='number').std() > 0.015 + another = df.select_dtypes(include='number').std() > 0.03# 0.015 return list(another[another].index) @callback( diff --git a/examples/perf_analyzer/runner_script.sh b/examples/perf_analyzer/runner_script.sh index 61d3b52b..2a63a90f 100644 --- a/examples/perf_analyzer/runner_script.sh +++ b/examples/perf_analyzer/runner_script.sh @@ -19,9 +19,9 @@ cp ../clover.in . make clean; make COMPILER=GNU; -echo "================================ Compile Done ================================ "l - -echo "============================= Running CloverLeaf ============================= "j +echo "================================ Compile Done ================================ " + +echo "============================= Running CloverLeaf ============================= " mpirun -np 2 tau_exec $SOURCE_BASE_DIRECTORY/clover_leaf pprof > tau_results cd - From c5094a54e847efa19130d216d4b0d9dd8b7af62e Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 26 Mar 2026 09:48:29 -0600 Subject: [PATCH 002/109] Version control first demo --- tools/version_control/dsi_vcs.py | 516 +++++++++++++++++++ tools/version_control/requirements.txt | 1 + tools/version_control/vcs_db.py | 94 ++++ tools/version_control/vcs_metadata_helper.py | 248 +++++++++ 4 files changed, 859 insertions(+) create mode 100755 tools/version_control/dsi_vcs.py create mode 100644 tools/version_control/requirements.txt create mode 100644 tools/version_control/vcs_db.py create mode 100644 tools/version_control/vcs_metadata_helper.py diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py new file mode 100755 index 00000000..44ebaa56 --- /dev/null +++ b/tools/version_control/dsi_vcs.py @@ -0,0 +1,516 @@ +#!/usr/bin/env python3 +""" +dsi_vcs.py — rsync-based file version control system +Captures full Linux file metadata (stat, ACL, xattrs), MD5 hash, +and stores versioned snapshots in SQLite. + +Usage: + python dsi_vcs.py init # init repo + python dsi_vcs.py commit [message] # commit a new version + python dsi_vcs.py log # list versions + python dsi_vcs.py diff # diff two versions + python dsi_vcs.py restore # restore a version + +Requirements: + pip install pyxattr # for extended attributes + sudo apt install acl # for getfacl (ACL support) + rsync must be installed # for snapshot copies +""" + +import os +import sys +import uuid +import subprocess +import json +import datetime +import argparse +from pathlib import Path +from typing import Optional + +from vcs_db import * +from vcs_metadata_helper import * + +# ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── + +def rsync_snapshot( + root_folder: str, + dest_path: str, + prev_snapshot: Optional[str] = None, + rel_paths: Optional[list[str]] = None, +) -> bool: + """ + Copy files from root_folder → dest_path using rsync hard-link deduplication. + If rel_paths is given, only those paths are synced (via --files-from). + If prev_snapshot is provided, unchanged files are hard-linked (saves disk). + """ + import tempfile + + os.makedirs(dest_path, exist_ok=True) + cmd = ["rsync", "-aAXH"] # for linux + if sys.platform == "darwin": + cmd = ["rsync", "-aEH"] # for macOS + + if prev_snapshot and os.path.isdir(prev_snapshot): + cmd += ["--link-dest", os.path.abspath(prev_snapshot)] + + # Write a temp file-list when only staging specific paths + files_from_tmp = None + if rel_paths is not None: + tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) + tmp.write("\n".join(rel_paths)) + tmp.close() + files_from_tmp = tmp.name + cmd += ["--files-from", files_from_tmp] + else: + cmd += [ + "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + ] + + cmd += [ + root_folder.rstrip("/") + "/", + dest_path.rstrip("/") + "/", + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): # 24 = some files vanished (ok) + print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) + return False + if sys.platform == "darwin": + for rel_path in rel_paths: + set_acl(dest_path, get_acl(rel_path) or "") + return True + finally: + if files_from_tmp: + os.unlink(files_from_tmp) + + +# ─────────────────────────── COMMANDS ──────────────────────────────────────── +class DSIVCS(): + + def __init__(self, folder: str): + self.root_folder = os.path.abspath(folder) + if not os.path.isdir(self.root_folder): + sys.exit(f"Error: '{self.root_folder}' is not a directory.") + + conn = open_db(self.root_folder) + conn.close() + print(f"Initialized dsi-vcs repository in: {self.root_folder}") + print(f" Snapshots: {self.root_folder}/{SNAPSHOTS_DIR}/") + + + def cmd_add(self, paths: list[str]): + """ + Stage one or more files/directories for the next commit. + Directories are expanded recursively; each resolved file is inserted into + the staging table. Adding an already-staged path is a silent no-op. + paths should be a list of relative paths to root_folder. + """ + # print(f"Staging {len(paths)} path(s) for commit…") + # print(paths) + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + added_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + staged = 0 + skip_names = {DB_NAME, SNAPSHOTS_DIR} + + def stage_path(abs_path: str): + nonlocal staged + # Reject paths outside the root + try: + os.path.relpath(abs_path, self.root_folder) + except ValueError: + print(f" [skip] {abs_path}: outside root folder") + return + + if not os.path.lexists(abs_path): + print(f" [skip] {abs_path}: path does not exist") + return + + cur.execute( + "INSERT OR IGNORE INTO staging (root_folder, absolute_path, added_at) " + "VALUES (?, ?, ?)", + (self.root_folder, abs_path, added_at) + ) + if cur.rowcount: + staged += 1 + + for raw in paths: + abs_path = os.path.join(self.root_folder, raw) + + if os.path.isdir(abs_path): + # Expand directory recursively + for dirpath, dirnames, filenames in os.walk(abs_path, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in skip_names] + for fname in filenames: + if fname in skip_names: + continue + stage_path(os.path.join(dirpath, fname)) + else: + stage_path(abs_path) + + conn.commit() + conn.close() + print(f" {staged} file(s) added to staging.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' to stage files.") + return + + print(f"Staged files ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel}") + + def cmd_remove(self, paths: list[str]): + """Remove file(s) from the staging area without touching the actual files.""" + root_folder = os.path.abspath(self.root_folder) + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + removed = 0 + + for raw in paths: + abs_path = os.path.abspath(raw) + cur.execute( + "DELETE FROM staging WHERE root_folder=? AND absolute_path=?", + (root_folder, abs_path) + ) + if cur.rowcount: + rel = os.path.relpath(abs_path, root_folder) + print(f" Unstaged: {rel}") + removed += 1 + else: + rel = os.path.relpath(abs_path, root_folder) + print(f" [not staged] {rel}") + + conn.commit() + conn.close() + print(f" {removed} file(s) removed from staging.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' to stage files.") + return + + print(f"Staged files ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel}") + + def cmd_commit(self, message: str = ""): + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + + # ── Load staged paths ──────────────────────────────────────────────────── + staged_rows = cur.execute( + "SELECT absolute_path FROM staging WHERE root_folder = ? ORDER BY absolute_path", + (self.root_folder,) + ).fetchall() + + if not staged_rows: + conn.close() + sys.exit("Nothing staged. Use 'add' to stage files before committing.") + + staged_paths = [r["absolute_path"] for r in staged_rows] + + # ── Previous snapshot for hard-link deduplication ──────────────────────── + prev_row = cur.execute( + "SELECT snapshot_path FROM versions " + "WHERE root_folder=? ORDER BY id DESC LIMIT 1", + (self.root_folder,) + ).fetchone() + prev_snapshot = prev_row["snapshot_path"] if prev_row else None + + committed_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + commit_hash = uuid.uuid4().hex + running_user = owner_name(os.getuid()) + snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) + + # ── Collect metadata for each staged file ─────────────────────────────── + print(f"Collecting metadata for {len(staged_paths)} staged file(s)…") + entries = [] + for abs_path in staged_paths: + e = collect_metadata(abs_path, self.root_folder) + if "error" in e: + print(f" [skip] {e['relative_path']}: {e['error']}") + else: + entries.append(e) + + if not entries: + conn.close() + sys.exit("No readable staged files — commit aborted.") + + total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") + file_count = sum(1 for e in entries if e.get("file_type") == "file") + + # ── rsync only the staged relative paths ──────────────────────────────── + rel_paths = [e["relative_path"] for e in entries] + print(f" {file_count} file(s), {total_bytes:,} bytes") + print(f" Creating rsync snapshot → {snapshot_path}") + + ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot, rel_paths) + if not ok: + conn.close() + sys.exit("rsync failed — commit aborted.") + + # ── Insert version row ─────────────────────────────────────────────────── + cur.execute( + """INSERT INTO versions + (root_folder, commit_hash, committed_at, owner_name, message, + snapshot_path, file_count, total_bytes) + VALUES (?,?,?,?,?,?,?,?)""", + (self.root_folder, commit_hash, committed_at, running_user, message, + snapshot_path, file_count, total_bytes) + ) + version_id = cur.lastrowid + + # ── Bulk-insert file entries ───────────────────────────────────────────── + cols = [ + "version_id", "root_folder", "relative_path", "absolute_path", + "file_name", "file_type", "md5_hash", + "lstat", + "permissions_octal", "permissions_str", "owner_name", "group_name", + "setuid", "setgid", "sticky", + "acl_text", "xattrs", "security_context", "symlink_target", + ] + placeholders = ",".join("?" * len(cols)) + col_str = ",".join(cols) + + cur.executemany( + f"INSERT INTO file_entries ({col_str}) VALUES ({placeholders})", + [ + tuple( + version_id if c == "version_id" else + self.root_folder if c == "root_folder" else + e.get(c) + for c in cols + ) + for e in entries + ] + ) + + # ── Clear staging after a successful commit ────────────────────────────── + cur.execute("DELETE FROM staging WHERE root_folder=?", (self.root_folder,)) + + conn.commit() + conn.close() + + print(f"\n Committed {commit_hash} at {committed_at}") + print(f" Short hash : {commit_hash[:12]}") + print(f" Owner : {running_user}") + if message: + print(f" Message : {message}") + + + def cmd_log(self): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT commit_hash, committed_at, owner_name, message, file_count, total_bytes " + "FROM versions WHERE root_folder=? ORDER BY id", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("No versions yet. Run 'commit' first.") + return + + print(f"{'COMMIT HASH':<34} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") + print("─" * 100) + for r in rows: + msg = (r["message"] or "")[:35] + print(f"{r['commit_hash']:<34} {r['owner_name']:<16} {r['committed_at']:<28}" + f"{r['file_count']:>7} {r['total_bytes']:>15,} {msg}") + + + def cmd_diff(self, c1: str, c2: str): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + + def get_files(chash): + vid = conn.execute( + "SELECT id FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, chash + "%") + ).fetchone() + if not vid: + conn.close() + sys.exit(f"Commit '{chash}' not found.") + rows = conn.execute( + "SELECT relative_path, file_type, md5_hash, permissions_octal, " + " owner_name, group_name, lstat " + "FROM file_entries WHERE version_id=?", (vid["id"],) + ).fetchall() + result = {} + for r in rows: + rec = dict(r) + # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. + rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} + result[r["relative_path"]] = rec + return result + + files1 = get_files(c1) + files2 = get_files(c2) + conn.close() + + all_paths = sorted(set(files1) | set(files2)) + added = deleted = modified = unchanged = 0 + + print(f"Diff {c1} → {c2} ({root_folder})\n") + print(f"{'STATUS':<10} {'PATH'}") + print("─" * 70) + + for p in all_paths: + # if p in files1 and p not in files2: + # print(f"{'DELETED':<10} {p:<40} in {c2}") + # deleted += 1 + # elif p in files2 and p not in files1: + # print(f"{'ADDED':<10} {p:<40} in {c2}") + # added += 1 + if p not in files1: + print(f"{'ADDED':<10} {p:<40} in {c2}") + added += 1 + elif p not in files2: + print(f"{'DELETED':<10} {p:<40} in {c2}") + deleted += 1 + else: + f1, f2 = files1[p], files2[p] + changes = [] + if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: + changes.append("content") + if f1["permissions_octal"] != f2["permissions_octal"]: + changes.append("perms") + if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: + changes.append("owner") + if f1["lstat"].get("st_size") != f2["lstat"].get("st_size"): + changes.append("size") + if changes: + print(f"{'MODIFIED':<10} {p} [{', '.join(changes)}]") + modified += 1 + else: + unchanged += 1 + + print(f"\nSummary: +{added} added -{deleted} deleted ~{modified} modified ={unchanged} unchanged") + + + def cmd_restore(self, commit_hash: str): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + row = conn.execute( + "SELECT commit_hash, snapshot_path FROM versions " + "WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, commit_hash + "%") + ).fetchone() + conn.close() + + if not row: + sys.exit(f"Commit '{commit_hash}' not found.") + + full_hash = row["commit_hash"] + snapshot = row["snapshot_path"] + if not os.path.isdir(snapshot): + sys.exit(f"Snapshot directory missing: {snapshot}") + + print(f"Restoring {commit_hash} from {snapshot} → {root_folder}") + cmd = [ + "rsync", "-aAXH", "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + snapshot.rstrip("/") + "/", + root_folder.rstrip("/") + "/", + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): + sys.exit(f"rsync restore failed:\n{result.stderr}") + print(f" Restored to {full_hash}") + + +# ─────────────────────────── CLI ───────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser( + description="dsi_vcs — rsync-based file version control with full Linux metadata" + ) + sub = parser.add_subparsers(dest="command", required=True) + + p_init = sub.add_parser("init", help="Initialize a new repo") + # p_init.add_argument("root_folder") + + p_add = sub.add_parser("add", help="add file or directory to staging") + p_add.add_argument('files', nargs='+', type=str) + + p_remove = sub.add_parser("remove", help="remove file or directory from staging") + p_remove.add_argument('files', nargs='+', type=str) + + p_commit = sub.add_parser("commit", help="Snapshot and commit staged files") + p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) + + p_log = sub.add_parser("log", help="List all commits") + + p_diff = sub.add_parser("diff", help="Diff two commits") + p_diff.add_argument("c1") + p_diff.add_argument("c2") + + p_restore = sub.add_parser("restore", help="Restore a version") + p_restore.add_argument("root_folder") + p_restore.add_argument("version") + + args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) + + # parser.print_help() + # parser.format_help() + if args.command == "init": + vcs = DSIVCS(os.getcwd()) + elif args.command == "add": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_add(args.files) + elif args.command == "remove": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_remove(args.files) + elif args.command == "commit": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_commit(args.message) + elif args.command == "diff": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_diff(args.c1, args.c2) + elif args.command == "log": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_log() + # elif args.command == "diff": cmd_diff(args.root_folder, args.v1, args.v2) + # elif args.command == "restore": cmd_restore(args.root_folder, args.version) + +if __name__ == "__main__": + # print("\n=== dsi-vcs: rsync-based file version control ===\n") + # print(os.getcwd()) + main() \ No newline at end of file diff --git a/tools/version_control/requirements.txt b/tools/version_control/requirements.txt new file mode 100644 index 00000000..11258685 --- /dev/null +++ b/tools/version_control/requirements.txt @@ -0,0 +1 @@ +pyxattr>=0.8.1 \ No newline at end of file diff --git a/tools/version_control/vcs_db.py b/tools/version_control/vcs_db.py new file mode 100644 index 00000000..72ef6990 --- /dev/null +++ b/tools/version_control/vcs_db.py @@ -0,0 +1,94 @@ +import sqlite3 +import os + +# ─────────────────────────── CONFIG ────────────────────────────────────────── + +DB_NAME = ".dsi_vcs.db" # SQLite DB stored inside the root folder +SNAPSHOTS_DIR = ".dsi_vcs_snapshots" # rsync snapshot copies live here + +# ─────────────────────────── DATABASE ──────────────────────────────────────── + +SCHEMA = """ +CREATE TABLE IF NOT EXISTS versions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + root_folder TEXT NOT NULL, + commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex + committed_at TEXT NOT NULL, -- ISO-8601 timestamp + owner_name TEXT NOT NULL, -- Linux user who ran the commit + message TEXT, + snapshot_path TEXT NOT NULL, -- path to rsync copy + file_count INTEGER NOT NULL, + total_bytes INTEGER NOT NULL, + UNIQUE(root_folder, commit_hash) +); + +CREATE TABLE IF NOT EXISTS file_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version_id INTEGER NOT NULL REFERENCES versions(id) ON DELETE CASCADE, + root_folder TEXT NOT NULL, -- partition key / lookup key + relative_path TEXT NOT NULL, -- path relative to root + absolute_path TEXT NOT NULL, + + -- ── Identity ────────────────────────────────────────────────────────── + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- file/dir/symlink/block/char/fifo/socket + + -- ── Content hash ────────────────────────────────────────────────────── + md5_hash TEXT, -- NULL for non-regular files + + -- ── lstat(2) — all raw os.lstat() fields packed as a JSON object ────── + -- Keys: st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, + -- st_atime, st_mtime, st_ctime, st_blocks, st_blksize + lstat TEXT NOT NULL, + + -- ── Human-readable permission strings ───────────────────────────────── + permissions_octal TEXT, -- e.g. "0o755" + permissions_str TEXT, -- e.g. "rwxr-xr-x" + owner_name TEXT, + group_name TEXT, + + -- ── Special bits ────────────────────────────────────────────────────── + setuid INTEGER DEFAULT 0, + setgid INTEGER DEFAULT 0, + sticky INTEGER DEFAULT 0, + + -- ── ACL (POSIX) ─────────────────────────────────────────────────────── + acl_text TEXT, -- raw getfacl output + + -- ── Extended attributes ─────────────────────────────────────────────── + xattrs TEXT, -- JSON dict of xattr key→value + + -- ── Symlink target ─────────────────────────────────────────────────── + symlink_target TEXT, + + -- ── SELinux / AppArmor context ──────────────────────────────────────── + security_context TEXT +); + +CREATE INDEX IF NOT EXISTS idx_file_entries_root + ON file_entries(root_folder, version_id); + +CREATE INDEX IF NOT EXISTS idx_file_entries_path + ON file_entries(root_folder, relative_path); + +CREATE TABLE IF NOT EXISTS staging ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + root_folder TEXT NOT NULL, + absolute_path TEXT NOT NULL, + added_at TEXT NOT NULL, -- ISO-8601 timestamp + UNIQUE(root_folder, absolute_path) +); +""" + + +def open_db(root_folder: str) -> sqlite3.Connection: + snaps = os.path.join(root_folder, SNAPSHOTS_DIR) + os.makedirs(snaps, exist_ok=True) + db_path = os.path.join(snaps, DB_NAME) + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA foreign_keys = ON") + conn.execute("PRAGMA journal_mode = WAL") + conn.executescript(SCHEMA) + conn.commit() + return conn diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py new file mode 100644 index 00000000..a70aeda8 --- /dev/null +++ b/tools/version_control/vcs_metadata_helper.py @@ -0,0 +1,248 @@ +import os +import stat +import hashlib +import sqlite3 +import json +import subprocess +import pwd +import grp +import sys +from typing import Optional + +# ─────────────────────────── METADATA HELPERS ──────────────────────────────── + +def file_type_str(mode: int) -> str: + if stat.S_ISREG(mode): return "file" + if stat.S_ISDIR(mode): return "dir" + if stat.S_ISLNK(mode): return "symlink" + if stat.S_ISBLK(mode): return "block" + if stat.S_ISCHR(mode): return "char" + if stat.S_ISFIFO(mode): return "fifo" + if stat.S_ISSOCK(mode): return "socket" + return "unknown" + + +def permission_str(mode: int) -> str: + """Convert mode bits → rwxrwxrwx string (no file-type prefix).""" + chars = [] + for who in (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, + stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, + stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH): + chars.append("+" if mode & who else "-") + # Replace x with s/t for setuid/setgid/sticky + if mode & stat.S_ISUID: + chars[2] = "s" if chars[2] == "+" else "S" + if mode & stat.S_ISGID: + chars[5] = "s" if chars[5] == "+" else "S" + if mode & stat.S_ISVTX: + chars[8] = "t" if chars[8] == "+" else "T" + return "".join(chars) + + +def owner_name(uid: int) -> str: + try: return pwd.getpwuid(uid).pw_name + except KeyError: return str(uid) + + +def group_name(gid: int) -> str: + try: return grp.getgrgid(gid).gr_name + except KeyError: return str(gid) + + +def get_acl(path: str) -> Optional[str]: + """Return raw getfacl output, or None if unavailable.""" + try: + if sys.platform == "darwin": + result = subprocess.run( + # ["getfacl", "--omit-header", "--absolute-names", path], + ["ls", "-le", path], + capture_output=True, text=True, timeout=5 + ) + lines = result.stdout.splitlines() + + acl_entries = [] + + for line in lines: + line = line.strip() + if line.startswith(tuple(str(i) + ":" for i in range(10))): + # Example line: "0: user:azad allow read,write" + parts = line.split("user:") + if len(parts) > 1: + acl_part = parts[1].strip() + acl_entries.append(acl_part) + + return ";".join(acl_entries) if acl_entries else None + else: + result = subprocess.run( + ["getfacl", "--omit-header", "--absolute-names", path], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() or None + except (FileNotFoundError, subprocess.TimeoutExpired): + return None + + +def set_acl(path: str, acl_string: str) -> Optional[str]: + """Return raw getfacl output, or None if unavailable.""" + acl_list = acl_string.split(";") + for acl_entry in acl_list: + try: + acls = acl_entry.split() + acl_perm = "user:" + acls[0] + " allow " + acls[2] + print(acl_perm) + result = subprocess.run( + ["chmod", "+a", acl_perm, path], + capture_output=True, text=True, timeout=5 + ) + if result.returncode != 0: + print(f"Failed to set ACL entry '{acl_entry}' on {path}: {result.stderr}") + except Exception as e: + print(f"Error setting ACL entry '{acl_entry}' on {path}: {e}") + + +def get_xattrs(path: str) -> Optional[str]: + """Return JSON dict of extended attributes, or None.""" + try: + import xattr # pyxattr + attrs = {} + for key in xattr.listxattr(path, symlink=True): + try: + val = xattr.getxattr(path, key, symlink=True) + attrs[key] = val.decode("utf-8", errors="replace") + except OSError: + attrs[key] = "" + return json.dumps(attrs) if attrs else None + except ImportError: + # Fallback: use getfattr + try: + result = subprocess.run( + ["getfattr", "-d", "-m", "-", "--absolute-names", path], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() or None + except FileNotFoundError: + return None + except OSError: + return None + + +def get_security_context(path: str) -> Optional[str]: + """Return SELinux/AppArmor security context if available.""" + try: + result = subprocess.run( + ["ls", "-Z", path], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + parts = result.stdout.strip().split() + # ls -Z format: context file (or just file if no context) + if len(parts) >= 2 and ":" in parts[0]: + return parts[0] + return None + except Exception: + return None + + +def md5_hash(path: str, chunk_size: int = 1 << 20) -> Optional[str]: + """Stream-hash a file in 1 MB chunks. Returns None on error.""" + h = hashlib.md5() + try: + with open(path, "rb") as f: + while chunk := f.read(chunk_size): + h.update(chunk) + return h.hexdigest() + except (PermissionError, OSError): + return None + + +def collect_metadata(abs_path: str, root_folder: str) -> dict: + """ + Gather all available metadata for a single path. + + Returns a dict with two logical groups: + • lstat — raw os.lstat() result stored as a nested dict (→ JSON column) + • all other keys — each stored in its own column + """ + rel_path = os.path.relpath(abs_path, root_folder) + + try: + s = os.lstat(abs_path) + except OSError as e: + return {"relative_path": rel_path, "error": str(e)} + + mode = s.st_mode + ftype = file_type_str(mode) + + # ── lstat dict (raw kernel values only) ────────────────────────────────── + lstat_dict = { + "st_mode": mode, + "st_ino": s.st_ino, + "st_dev": s.st_dev, + "st_nlink": s.st_nlink, + "st_uid": s.st_uid, + "st_gid": s.st_gid, + "st_size": s.st_size, + "st_atime": s.st_atime, + "st_mtime": s.st_mtime, + "st_ctime": s.st_ctime, + "st_blocks": getattr(s, "st_blocks", None), + "st_blksize": getattr(s, "st_blksize", None), + } + + # ── all other (derived / external) attributes — own columns ────────────── + entry = { + "relative_path": rel_path, + "absolute_path": abs_path, + "file_name": os.path.basename(abs_path), + "file_type": ftype, + + # serialised lstat dict → stored as TEXT (JSON) in a single column + "lstat": json.dumps(lstat_dict), + + # human-readable permission strings + "permissions_octal": oct(stat.S_IMODE(mode)), + "permissions_str": permission_str(mode), + "owner_name": owner_name(s.st_uid), + "group_name": group_name(s.st_gid), + + # special-bit flags + "setuid": int(bool(mode & stat.S_ISUID)), + "setgid": int(bool(mode & stat.S_ISGID)), + "sticky": int(bool(mode & stat.S_ISVTX)), + + # external / subprocess-derived attributes + "acl_text": get_acl(abs_path), + "xattrs": get_xattrs(abs_path), + "security_context": get_security_context(abs_path), + "symlink_target": os.readlink(abs_path) if ftype == "symlink" else None, + + # content hash — regular files only + "md5_hash": md5_hash(abs_path) if ftype == "file" else None, + + # convenience shortcut used by walk_folder aggregation (not stored) + "_st_size": s.st_size, + } + print(f"ACL text for {rel_path}: {entry['acl_text']}") + return entry + + +# def walk_folder(root_folder: str) -> list[dict]: +# """Recursively collect metadata for every entry under root_folder.""" +# entries = [] +# skip_dirs = {DB_NAME, SNAPSHOTS_DIR} + +# for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): +# # Prune internal bookkeeping dirs +# dirnames[:] = [d for d in dirnames if d not in skip_dirs] + +# # Include the directory entry itself (except root) +# if dirpath != root_folder: +# entries.append(collect_metadata(dirpath, root_folder)) + +# for fname in filenames: +# if fname == DB_NAME: +# continue +# abs_path = os.path.join(dirpath, fname) +# entries.append(collect_metadata(abs_path, root_folder)) + +# return entries From 0ac7cdb7ecbd7e91a93828da6a6fe1c884ee5e70 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 26 Mar 2026 19:25:27 -0600 Subject: [PATCH 003/109] Fixed acl in versioning --- tools/version_control/vcs_metadata_helper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index a70aeda8..4eae1bcd 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -87,6 +87,8 @@ def set_acl(path: str, acl_string: str) -> Optional[str]: acl_list = acl_string.split(";") for acl_entry in acl_list: try: + if len(acl_entry) == 0: + continue acls = acl_entry.split() acl_perm = "user:" + acls[0] + " allow " + acls[2] print(acl_perm) From 138b67470519883197d429ee03d4eef3f715c2ff Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 08:54:51 -0600 Subject: [PATCH 004/109] VCS: Fix permission str and cli usage. --- tools/version_control/dsi_vcs.py | 22 +++++++++++--------- tools/version_control/vcs_metadata_helper.py | 17 ++++++++------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index 44ebaa56..80a1e551 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -5,11 +5,13 @@ and stores versioned snapshots in SQLite. Usage: - python dsi_vcs.py init # init repo - python dsi_vcs.py commit [message] # commit a new version - python dsi_vcs.py log # list versions - python dsi_vcs.py diff # diff two versions - python dsi_vcs.py restore # restore a version + python dsi_vcs.py init # init repo in current directory + python dsi_vcs.py add ... # stage paths for the next commit + python dsi_vcs.py remove ... # unstage paths + python dsi_vcs.py commit [message] # commit a new version + python dsi_vcs.py log # list versions + python dsi_vcs.py diff # diff two versions + python dsi_vcs.py restore # restore a version Requirements: pip install pyxattr # for extended attributes @@ -176,7 +178,7 @@ def stage_path(abs_path: str): for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") - + def cmd_remove(self, paths: list[str]): """Remove file(s) from the staging area without touching the actual files.""" root_folder = os.path.abspath(self.root_folder) @@ -483,7 +485,6 @@ def main(): p_diff.add_argument("c2") p_restore = sub.add_parser("restore", help="Restore a version") - p_restore.add_argument("root_folder") p_restore.add_argument("version") args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) @@ -507,10 +508,11 @@ def main(): elif args.command == "log": vcs = DSIVCS(os.getcwd()) vcs.cmd_log() - # elif args.command == "diff": cmd_diff(args.root_folder, args.v1, args.v2) - # elif args.command == "restore": cmd_restore(args.root_folder, args.version) + elif args.command == "restore": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_restore(args.version) if __name__ == "__main__": # print("\n=== dsi-vcs: rsync-based file version control ===\n") # print(os.getcwd()) - main() \ No newline at end of file + main() diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 4eae1bcd..6daea75b 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -25,17 +25,19 @@ def file_type_str(mode: int) -> str: def permission_str(mode: int) -> str: """Convert mode bits → rwxrwxrwx string (no file-type prefix).""" chars = [] - for who in (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, - stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, - stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH): - chars.append("+" if mode & who else "-") + for bit, char in ( + (stat.S_IRUSR, "r"), (stat.S_IWUSR, "w"), (stat.S_IXUSR, "x"), + (stat.S_IRGRP, "r"), (stat.S_IWGRP, "w"), (stat.S_IXGRP, "x"), + (stat.S_IROTH, "r"), (stat.S_IWOTH, "w"), (stat.S_IXOTH, "x"), + ): + chars.append(char if mode & bit else "-") # Replace x with s/t for setuid/setgid/sticky if mode & stat.S_ISUID: - chars[2] = "s" if chars[2] == "+" else "S" + chars[2] = "s" if chars[2] == "x" else "S" if mode & stat.S_ISGID: - chars[5] = "s" if chars[5] == "+" else "S" + chars[5] = "s" if chars[5] == "x" else "S" if mode & stat.S_ISVTX: - chars[8] = "t" if chars[8] == "+" else "T" + chars[8] = "t" if chars[8] == "x" else "T" return "".join(chars) @@ -224,7 +226,6 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: # convenience shortcut used by walk_folder aggregation (not stored) "_st_size": s.st_size, } - print(f"ACL text for {rel_path}: {entry['acl_text']}") return entry From 94f7db20b9ba640ac2b15818d52451fb873935f6 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 09:14:55 -0600 Subject: [PATCH 005/109] VCS: create complete snapshots. The rel_path branch results in only including staged files in the snapshot. Complete snapshots of the remote folder are needed so unchanged files are not omitted in a snapshot. --- tools/version_control/dsi_vcs.py | 71 ++++++++------------ tools/version_control/vcs_metadata_helper.py | 19 ++++++ 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index 80a1e551..ef185f50 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -38,15 +38,11 @@ def rsync_snapshot( root_folder: str, dest_path: str, prev_snapshot: Optional[str] = None, - rel_paths: Optional[list[str]] = None, ) -> bool: """ - Copy files from root_folder → dest_path using rsync hard-link deduplication. - If rel_paths is given, only those paths are synced (via --files-from). + Copy the full root_folder tree to dest_path using rsync hard-link deduplication. If prev_snapshot is provided, unchanged files are hard-linked (saves disk). """ - import tempfile - os.makedirs(dest_path, exist_ok=True) cmd = ["rsync", "-aAXH"] # for linux if sys.platform == "darwin": @@ -55,38 +51,22 @@ def rsync_snapshot( if prev_snapshot and os.path.isdir(prev_snapshot): cmd += ["--link-dest", os.path.abspath(prev_snapshot)] - # Write a temp file-list when only staging specific paths - files_from_tmp = None - if rel_paths is not None: - tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) - tmp.write("\n".join(rel_paths)) - tmp.close() - files_from_tmp = tmp.name - cmd += ["--files-from", files_from_tmp] - else: - cmd += [ - "--delete", - "--exclude", DB_NAME, - "--exclude", SNAPSHOTS_DIR, - ] + cmd += [ + "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + ] cmd += [ root_folder.rstrip("/") + "/", dest_path.rstrip("/") + "/", ] - try: - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode not in (0, 24): # 24 = some files vanished (ok) - print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) - return False - if sys.platform == "darwin": - for rel_path in rel_paths: - set_acl(dest_path, get_acl(rel_path) or "") - return True - finally: - if files_from_tmp: - os.unlink(files_from_tmp) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): # 24 = some files vanished (ok) + print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) + return False + return True # ─────────────────────────── COMMANDS ──────────────────────────────────────── @@ -259,33 +239,38 @@ def cmd_commit(self, message: str = ""): running_user = owner_name(os.getuid()) snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) - # ── Collect metadata for each staged file ─────────────────────────────── - print(f"Collecting metadata for {len(staged_paths)} staged file(s)…") - entries = [] + # ── Validate staged files before creating the snapshot ───────────────── + print(f"Validating {len(staged_paths)} staged file(s)…") + valid_staged = 0 for abs_path in staged_paths: e = collect_metadata(abs_path, self.root_folder) if "error" in e: print(f" [skip] {e['relative_path']}: {e['error']}") else: - entries.append(e) + valid_staged += 1 - if not entries: + if valid_staged == 0: conn.close() sys.exit("No readable staged files — commit aborted.") - total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") - file_count = sum(1 for e in entries if e.get("file_type") == "file") - - # ── rsync only the staged relative paths ──────────────────────────────── - rel_paths = [e["relative_path"] for e in entries] - print(f" {file_count} file(s), {total_bytes:,} bytes") + # ── Create a complete snapshot of the current repository tree ─────────── print(f" Creating rsync snapshot → {snapshot_path}") - ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot, rel_paths) + ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot) if not ok: conn.close() sys.exit("rsync failed — commit aborted.") + # ── Collect metadata for the complete committed tree ─────────────────── + entries = collect_root_file_metadata(self.root_folder) + if not entries: + conn.close() + sys.exit("No readable files — commit aborted.") + + total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") + file_count = sum(1 for e in entries if e.get("file_type") == "file") + print(f" {file_count} file(s), {total_bytes:,} bytes") + # ── Insert version row ─────────────────────────────────────────────────── cur.execute( """INSERT INTO versions diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 6daea75b..122b765c 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,6 +229,25 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry +def collect_root_file_metadata(root_folder: str) -> list[dict]: + """Collect metadata for every tracked file under root_folder.""" + entries = [] + skip_names = {DB_NAME, SNAPSHOTS_DIR} + + for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in skip_names] + for fname in filenames: + if fname in skip_names: + continue + entry = collect_metadata(os.path.join(dirpath, fname), root_folder) + if "error" in entry: + print(f" [skip] {entry['relative_path']}: {entry['error']}") + else: + entries.append(entry) + + return entries + + # def walk_folder(root_folder: str) -> list[dict]: # """Recursively collect metadata for every entry under root_folder.""" # entries = [] From 69ea3fb1bb8552e4b1797bb755929a3a2d328163 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 09:26:24 -0600 Subject: [PATCH 006/109] VCS: track directory metadata. Previous metadata tracking was only for files. This tracks paths (both files and directories) so we can preserve permissions for directory trees. --- tools/version_control/dsi_vcs.py | 23 ++++++++++---------- tools/version_control/vcs_metadata_helper.py | 12 ++++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index ef185f50..a5476aa0 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -130,6 +130,7 @@ def stage_path(abs_path: str): # Expand directory recursively for dirpath, dirnames, filenames in os.walk(abs_path, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] + stage_path(dirpath) for fname in filenames: if fname in skip_names: continue @@ -139,7 +140,7 @@ def stage_path(abs_path: str): conn.commit() conn.close() - print(f" {staged} file(s) added to staging.") + print(f" {staged} path(s) added to staging.") """Show files currently in the staging area.""" root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) @@ -151,16 +152,16 @@ def stage_path(abs_path: str): conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage files.") + print("Nothing staged. Use 'add ...' to stage paths.") return - print(f"Staged files ({len(rows)}):") + print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") def cmd_remove(self, paths: list[str]): - """Remove file(s) from the staging area without touching the actual files.""" + """Remove path(s) from the staging area without touching the actual files.""" root_folder = os.path.abspath(self.root_folder) db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) if not os.path.isfile(db_path): @@ -186,7 +187,7 @@ def cmd_remove(self, paths: list[str]): conn.commit() conn.close() - print(f" {removed} file(s) removed from staging.") + print(f" {removed} path(s) removed from staging.") """Show files currently in the staging area.""" root_folder = os.path.abspath(root_folder) conn = open_db(root_folder) @@ -198,10 +199,10 @@ def cmd_remove(self, paths: list[str]): conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage files.") + print("Nothing staged. Use 'add ...' to stage paths.") return - print(f"Staged files ({len(rows)}):") + print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") @@ -239,8 +240,8 @@ def cmd_commit(self, message: str = ""): running_user = owner_name(os.getuid()) snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) - # ── Validate staged files before creating the snapshot ───────────────── - print(f"Validating {len(staged_paths)} staged file(s)…") + # ── Validate staged paths before creating the snapshot ───────────────── + print(f"Validating {len(staged_paths)} staged path(s)…") valid_staged = 0 for abs_path in staged_paths: e = collect_metadata(abs_path, self.root_folder) @@ -251,7 +252,7 @@ def cmd_commit(self, message: str = ""): if valid_staged == 0: conn.close() - sys.exit("No readable staged files — commit aborted.") + sys.exit("No readable staged paths — commit aborted.") # ── Create a complete snapshot of the current repository tree ─────────── print(f" Creating rsync snapshot → {snapshot_path}") @@ -262,7 +263,7 @@ def cmd_commit(self, message: str = ""): sys.exit("rsync failed — commit aborted.") # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_root_file_metadata(self.root_folder) + entries = collect_root_metadata(self.root_folder) if not entries: conn.close() sys.exit("No readable files — commit aborted.") diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 122b765c..819bf100 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,13 +229,21 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry -def collect_root_file_metadata(root_folder: str) -> list[dict]: - """Collect metadata for every tracked file under root_folder.""" + +def collect_root_metadata(root_folder: str) -> list[dict]: + """Collect metadata for every tracked directory and file under root_folder.""" entries = [] skip_names = {DB_NAME, SNAPSHOTS_DIR} for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] + if dirpath != root_folder: + entry = collect_metadata(dirpath, root_folder) + if "error" in entry: + print(f" [skip] {entry['relative_path']}: {entry['error']}") + else: + entries.append(entry) + for fname in filenames: if fname in skip_names: continue From e5fdadc1e4d816628c4837e04dad0132b2af4dbe Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 10:19:56 -0600 Subject: [PATCH 007/109] VCS: add delete command for staging path deletion. --- tools/version_control/dsi_vcs.py | 127 +++++++++++++++---- tools/version_control/vcs_db.py | 1 + tools/version_control/vcs_metadata_helper.py | 16 ++- 3 files changed, 109 insertions(+), 35 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index a5476aa0..53901553 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -7,6 +7,7 @@ Usage: python dsi_vcs.py init # init repo in current directory python dsi_vcs.py add ... # stage paths for the next commit + python dsi_vcs.py delete ... # stage paths for deletion python dsi_vcs.py remove ... # unstage paths python dsi_vcs.py commit [message] # commit a new version python dsi_vcs.py log # list versions @@ -26,6 +27,7 @@ import json import datetime import argparse +import shutil from pathlib import Path from typing import Optional @@ -69,6 +71,26 @@ def rsync_snapshot( return True +def snapshot_target(snapshot_path: str, relative_path: str) -> str: + target = os.path.abspath(os.path.join(snapshot_path, relative_path)) + snapshot_root = os.path.abspath(snapshot_path) + if os.path.commonpath([snapshot_root, target]) != snapshot_root: + raise ValueError(f"Snapshot path escapes snapshot root: {relative_path}") + return target + + +def apply_snapshot_deletes(snapshot_path: str, root_folder: str, staged_deletes: list[str]) -> None: + for abs_path in staged_deletes: + rel_path = os.path.relpath(abs_path, root_folder) + if rel_path in ("", "."): + raise ValueError("Refusing to stage repository root for deletion.") + target = snapshot_target(snapshot_path, rel_path) + if os.path.isdir(target) and not os.path.islink(target): + shutil.rmtree(target) + elif os.path.lexists(target): + os.unlink(target) + + # ─────────────────────────── COMMANDS ──────────────────────────────────────── class DSIVCS(): @@ -104,27 +126,22 @@ def cmd_add(self, paths: list[str]): def stage_path(abs_path: str): nonlocal staged - # Reject paths outside the root - try: - os.path.relpath(abs_path, self.root_folder) - except ValueError: - print(f" [skip] {abs_path}: outside root folder") - return + abs_path = os.path.abspath(abs_path) if not os.path.lexists(abs_path): print(f" [skip] {abs_path}: path does not exist") return cur.execute( - "INSERT OR IGNORE INTO staging (root_folder, absolute_path, added_at) " - "VALUES (?, ?, ?)", - (self.root_folder, abs_path, added_at) + "INSERT OR REPLACE INTO staging (root_folder, absolute_path, action, added_at) " + "VALUES (?, ?, ?, ?)", + (self.root_folder, abs_path, "add", added_at) ) if cur.rowcount: staged += 1 for raw in paths: - abs_path = os.path.join(self.root_folder, raw) + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(self.root_folder, raw)) if os.path.isdir(abs_path): # Expand directory recursively @@ -145,20 +162,63 @@ def stage_path(abs_path: str): root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) rows = conn.execute( - "SELECT absolute_path, added_at FROM staging " + "SELECT absolute_path, action, added_at FROM staging " "WHERE root_folder=? ORDER BY absolute_path", (root_folder,) ).fetchall() conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage paths.") + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") return print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) - print(f" {rel}") + print(f" {rel} [{r['action']}]") + + def cmd_delete(self, paths: list[str]): + """Stage path(s) for deletion in the next commit.""" + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + added_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + staged = 0 + + for raw in paths: + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(self.root_folder, raw)) + cur.execute( + "INSERT OR REPLACE INTO staging (root_folder, absolute_path, action, added_at) " + "VALUES (?, ?, ?, ?)", + (self.root_folder, abs_path, "delete", added_at) + ) + if cur.rowcount: + staged += 1 + + conn.commit() + conn.close() + print(f" {staged} path(s) staged for deletion.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, action, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") + return + + print(f"Staged paths ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel} [{r['action']}]") def cmd_remove(self, paths: list[str]): """Remove path(s) from the staging area without touching the actual files.""" @@ -172,7 +232,7 @@ def cmd_remove(self, paths: list[str]): removed = 0 for raw in paths: - abs_path = os.path.abspath(raw) + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(root_folder, raw)) cur.execute( "DELETE FROM staging WHERE root_folder=? AND absolute_path=?", (root_folder, abs_path) @@ -192,20 +252,20 @@ def cmd_remove(self, paths: list[str]): root_folder = os.path.abspath(root_folder) conn = open_db(root_folder) rows = conn.execute( - "SELECT absolute_path, added_at FROM staging " + "SELECT absolute_path, action, added_at FROM staging " "WHERE root_folder=? ORDER BY absolute_path", (root_folder,) ).fetchall() conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage paths.") + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") return print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) - print(f" {rel}") + print(f" {rel} [{r['action']}]") def cmd_commit(self, message: str = ""): db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) @@ -217,15 +277,16 @@ def cmd_commit(self, message: str = ""): # ── Load staged paths ──────────────────────────────────────────────────── staged_rows = cur.execute( - "SELECT absolute_path FROM staging WHERE root_folder = ? ORDER BY absolute_path", + "SELECT absolute_path, action FROM staging WHERE root_folder = ? ORDER BY absolute_path", (self.root_folder,) ).fetchall() if not staged_rows: conn.close() - sys.exit("Nothing staged. Use 'add' to stage files before committing.") + sys.exit("Nothing staged. Use 'add' or 'delete' before committing.") - staged_paths = [r["absolute_path"] for r in staged_rows] + staged_adds = [r["absolute_path"] for r in staged_rows if r["action"] == "add"] + staged_deletes = [r["absolute_path"] for r in staged_rows if r["action"] == "delete"] # ── Previous snapshot for hard-link deduplication ──────────────────────── prev_row = cur.execute( @@ -241,9 +302,9 @@ def cmd_commit(self, message: str = ""): snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) # ── Validate staged paths before creating the snapshot ───────────────── - print(f"Validating {len(staged_paths)} staged path(s)…") - valid_staged = 0 - for abs_path in staged_paths: + print(f"Validating {len(staged_rows)} staged path(s)…") + valid_staged = len(staged_deletes) + for abs_path in staged_adds: e = collect_metadata(abs_path, self.root_folder) if "error" in e: print(f" [skip] {e['relative_path']}: {e['error']}") @@ -262,11 +323,15 @@ def cmd_commit(self, message: str = ""): conn.close() sys.exit("rsync failed — commit aborted.") - # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_root_metadata(self.root_folder) - if not entries: + try: + apply_snapshot_deletes(snapshot_path, self.root_folder, staged_deletes) + except ValueError as e: conn.close() - sys.exit("No readable files — commit aborted.") + shutil.rmtree(snapshot_path, ignore_errors=True) + sys.exit(str(e)) + + # ── Collect metadata for the complete committed tree ─────────────────── + entries = collect_tree_metadata(snapshot_path, self.root_folder) total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") file_count = sum(1 for e in entries if e.get("file_type") == "file") @@ -458,10 +523,13 @@ def main(): p_add = sub.add_parser("add", help="add file or directory to staging") p_add.add_argument('files', nargs='+', type=str) + p_delete = sub.add_parser("delete", help="stage file or directory deletion") + p_delete.add_argument('files', nargs='+', type=str) + p_remove = sub.add_parser("remove", help="remove file or directory from staging") p_remove.add_argument('files', nargs='+', type=str) - p_commit = sub.add_parser("commit", help="Snapshot and commit staged files") + p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) p_log = sub.add_parser("log", help="List all commits") @@ -482,6 +550,9 @@ def main(): elif args.command == "add": vcs = DSIVCS(os.getcwd()) vcs.cmd_add(args.files) + elif args.command == "delete": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_delete(args.files) elif args.command == "remove": vcs = DSIVCS(os.getcwd()) vcs.cmd_remove(args.files) diff --git a/tools/version_control/vcs_db.py b/tools/version_control/vcs_db.py index 72ef6990..aaf21fca 100644 --- a/tools/version_control/vcs_db.py +++ b/tools/version_control/vcs_db.py @@ -75,6 +75,7 @@ id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, absolute_path TEXT NOT NULL, + action TEXT NOT NULL DEFAULT 'add', added_at TEXT NOT NULL, -- ISO-8601 timestamp UNIQUE(root_folder, absolute_path) ); diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 819bf100..a8734f7f 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,33 +229,35 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry - -def collect_root_metadata(root_folder: str) -> list[dict]: - """Collect metadata for every tracked directory and file under root_folder.""" +def collect_tree_metadata(scan_root: str, identity_root: str) -> list[dict]: + """Collect metadata under scan_root while storing paths under identity_root.""" entries = [] skip_names = {DB_NAME, SNAPSHOTS_DIR} - for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + for dirpath, dirnames, filenames in os.walk(scan_root, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] - if dirpath != root_folder: - entry = collect_metadata(dirpath, root_folder) + if dirpath != scan_root: + entry = collect_metadata(dirpath, scan_root) if "error" in entry: print(f" [skip] {entry['relative_path']}: {entry['error']}") else: + entry["absolute_path"] = os.path.join(identity_root, entry["relative_path"]) entries.append(entry) for fname in filenames: if fname in skip_names: continue - entry = collect_metadata(os.path.join(dirpath, fname), root_folder) + entry = collect_metadata(os.path.join(dirpath, fname), scan_root) if "error" in entry: print(f" [skip] {entry['relative_path']}: {entry['error']}") else: + entry["absolute_path"] = os.path.join(identity_root, entry["relative_path"]) entries.append(entry) return entries + # def walk_folder(root_folder: str) -> list[dict]: # """Recursively collect metadata for every entry under root_folder.""" # entries = [] From c7ed643e3ebc5cdf92a141c025f8ecb9c98c2311 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 13:44:55 -0600 Subject: [PATCH 008/109] VCS: move dsi_vcs utility under dsi, begin tests. This moves the dsi_vcs into dsi as a package under utils so it will be installed and available. This also begins tests for the utility. --- dsi/utils/version_control/__init__.py | 3 + dsi/utils/version_control/__main__.py | 5 ++ dsi/utils/version_control/cli.py | 68 ++++++++++++++++++ .../utils}/version_control/dsi_vcs.py | 71 +------------------ .../utils}/version_control/requirements.txt | 0 dsi/utils/version_control/tests/test_vcs.py | 70 ++++++++++++++++++ .../utils}/version_control/vcs_db.py | 0 .../version_control/vcs_metadata_helper.py | 2 + pyproject.toml | 3 +- 9 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 dsi/utils/version_control/__init__.py create mode 100644 dsi/utils/version_control/__main__.py create mode 100644 dsi/utils/version_control/cli.py rename {tools => dsi/utils}/version_control/dsi_vcs.py (88%) rename {tools => dsi/utils}/version_control/requirements.txt (100%) create mode 100644 dsi/utils/version_control/tests/test_vcs.py rename {tools => dsi/utils}/version_control/vcs_db.py (100%) rename {tools => dsi/utils}/version_control/vcs_metadata_helper.py (99%) diff --git a/dsi/utils/version_control/__init__.py b/dsi/utils/version_control/__init__.py new file mode 100644 index 00000000..64b89704 --- /dev/null +++ b/dsi/utils/version_control/__init__.py @@ -0,0 +1,3 @@ +from .dsi_vcs import DSIVCS + +__all__ = ["DSIVCS"] diff --git a/dsi/utils/version_control/__main__.py b/dsi/utils/version_control/__main__.py new file mode 100644 index 00000000..2efe6923 --- /dev/null +++ b/dsi/utils/version_control/__main__.py @@ -0,0 +1,5 @@ +from .cli import main + +if __name__ == "__main__": + main() + diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py new file mode 100644 index 00000000..97823901 --- /dev/null +++ b/dsi/utils/version_control/cli.py @@ -0,0 +1,68 @@ +import argparse +import datetime +import sys + +from .dsi_vcs import DSIVCS + +def main(): + parser = argparse.ArgumentParser( + description="dsi-vcs — rsync-based file version control with full Linux metadata" + ) + sub = parser.add_subparsers(dest="command", required=True) + + p_init = sub.add_parser("init", help="Initialize a new repo") + # p_init.add_argument("root_folder") + + p_add = sub.add_parser("add", help="add file or directory to staging") + p_add.add_argument('files', nargs='+', type=str) + + p_delete = sub.add_parser("delete", help="stage file or directory deletion") + p_delete.add_argument('files', nargs='+', type=str) + + p_remove = sub.add_parser("remove", help="remove file or directory from staging") + p_remove.add_argument('files', nargs='+', type=str) + + p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") + p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) + + p_log = sub.add_parser("log", help="List all commits") + + p_diff = sub.add_parser("diff", help="Diff two commits") + p_diff.add_argument("c1") + p_diff.add_argument("c2") + + p_restore = sub.add_parser("restore", help="Restore a version") + p_restore.add_argument("version") + + args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) + + # parser.print_help() + # parser.format_help() + if args.command == "init": + vcs = DSIVCS(os.getcwd()) + elif args.command == "add": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_add(args.files) + elif args.command == "delete": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_delete(args.files) + elif args.command == "remove": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_remove(args.files) + elif args.command == "commit": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_commit(args.message) + elif args.command == "diff": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_diff(args.c1, args.c2) + elif args.command == "log": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_log() + elif args.command == "restore": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_restore(args.version) + +if __name__ == "__main__": + # print("\n=== dsi-vcs: rsync-based file version control ===\n") + # print(os.getcwd()) + main() diff --git a/tools/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py similarity index 88% rename from tools/version_control/dsi_vcs.py rename to dsi/utils/version_control/dsi_vcs.py index 53901553..bc30538a 100755 --- a/tools/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -26,13 +26,12 @@ import subprocess import json import datetime -import argparse import shutil from pathlib import Path from typing import Optional -from vcs_db import * -from vcs_metadata_helper import * +from .vcs_db import * +from .vcs_metadata_helper import * # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -507,69 +506,3 @@ def cmd_restore(self, commit_hash: str): if result.returncode not in (0, 24): sys.exit(f"rsync restore failed:\n{result.stderr}") print(f" Restored to {full_hash}") - - -# ─────────────────────────── CLI ───────────────────────────────────────────── - -def main(): - parser = argparse.ArgumentParser( - description="dsi_vcs — rsync-based file version control with full Linux metadata" - ) - sub = parser.add_subparsers(dest="command", required=True) - - p_init = sub.add_parser("init", help="Initialize a new repo") - # p_init.add_argument("root_folder") - - p_add = sub.add_parser("add", help="add file or directory to staging") - p_add.add_argument('files', nargs='+', type=str) - - p_delete = sub.add_parser("delete", help="stage file or directory deletion") - p_delete.add_argument('files', nargs='+', type=str) - - p_remove = sub.add_parser("remove", help="remove file or directory from staging") - p_remove.add_argument('files', nargs='+', type=str) - - p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") - p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) - - p_log = sub.add_parser("log", help="List all commits") - - p_diff = sub.add_parser("diff", help="Diff two commits") - p_diff.add_argument("c1") - p_diff.add_argument("c2") - - p_restore = sub.add_parser("restore", help="Restore a version") - p_restore.add_argument("version") - - args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) - - # parser.print_help() - # parser.format_help() - if args.command == "init": - vcs = DSIVCS(os.getcwd()) - elif args.command == "add": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_add(args.files) - elif args.command == "delete": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_delete(args.files) - elif args.command == "remove": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_remove(args.files) - elif args.command == "commit": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_commit(args.message) - elif args.command == "diff": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_diff(args.c1, args.c2) - elif args.command == "log": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_log() - elif args.command == "restore": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_restore(args.version) - -if __name__ == "__main__": - # print("\n=== dsi-vcs: rsync-based file version control ===\n") - # print(os.getcwd()) - main() diff --git a/tools/version_control/requirements.txt b/dsi/utils/version_control/requirements.txt similarity index 100% rename from tools/version_control/requirements.txt rename to dsi/utils/version_control/requirements.txt diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py new file mode 100644 index 00000000..c1b961ba --- /dev/null +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -0,0 +1,70 @@ +import os +import sqlite3 +from pathlib import Path +from shutil import which +import pytest +import stat + +from dsi.utils.version_control.dsi_vcs import DSIVCS +from dsi.utils.version_control.vcs_db import DB_NAME, SNAPSHOTS_DIR + +def require_rsync(): + if which("rsync") is None: + pytest.skip("rsync is required for dsi_vcs.") + + + +def connect_repo(repo_path): + return sqlite3.connect(repo_path / SNAPSHOTS_DIR / DB_NAME) + +def commits(repo_path): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return conn.execute( + "SELECT id, commit_hash, snapshot_path FROM versions ORDER BY id" + ).fetchall() + +def latest_entries(repo_path): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return conn.execute( + "SELECT relative_path, file_type, permissions_octal, permissions_str, setgid " + "FROM file_entries WHERE version_id=(SELECT MAX(id) from versions) " + "ORDER BY relative_path").fetchall() + +def test_add(tmp_path): + require_rsync() + repo = DSIVCS(str(tmp_path)) + + empty_dir = tmp_path / "empty" + nested_dir = tmp_path / "nested" / "child" + empty_dir.mkdir() + nested_dir.mkdir(parents=True) + (tmp_path / "a.txt").write_text("foo") + (nested_dir / "b.txt").write_text("bar") + + empty_dir.chmod(0o2775) + + repo.cmd_add(["a.txt", "nested", "empty"]) + repo.cmd_commit("alpha") + + (tmp_path / "c.txt").write_text("baz") + + repo.cmd_add(["c.txt"]) + repo.cmd_commit("beta") + + alpha, beta = commits(tmp_path) + alpha_path = Path(alpha["snapshot_path"]) + beta_path = Path(beta["snapshot_path"]) + assert (alpha_path / "a.txt").read_text() == "foo" + assert (alpha_path / "nested" / "child" / "b.txt").read_text() == "bar" + assert (beta_path / "c.txt").read_text() == "baz" + assert (alpha_path / "empty").exists() + assert stat.S_IMODE((alpha_path / "empty").stat().st_mode) == 0o2775 + + rows = {row["relative_path"]: row for row in latest_entries(tmp_path)} + empty_entry = rows["empty"] + assert empty_entry["file_type"] == "dir" + assert empty_entry["permissions_str"] == "rwxrwsr-x" + assert eval(empty_entry["permissions_octal"]) == 0o2775 + assert empty_entry["setgid"] == 1 diff --git a/tools/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py similarity index 100% rename from tools/version_control/vcs_db.py rename to dsi/utils/version_control/vcs_db.py diff --git a/tools/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py similarity index 99% rename from tools/version_control/vcs_metadata_helper.py rename to dsi/utils/version_control/vcs_metadata_helper.py index a8734f7f..9b756f3b 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -9,6 +9,8 @@ import sys from typing import Optional +from .vcs_db import DB_NAME, SNAPSHOTS_DIR + # ─────────────────────────── METADATA HELPERS ──────────────────────────────── def file_type_str(mode: int) -> str: diff --git a/pyproject.toml b/pyproject.toml index 11c6bbaa..c53ca221 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ include-package-data = true [project.scripts] dsi = "dsi.cli:main" +dsi-vcs = "dsi.utils.version_control.cli:main" [tool.setuptools.packages.find] where = ["."] @@ -36,4 +37,4 @@ include = ["dsi*"] exclude = ["clover3d"] [tool.setuptools.package-data] -dsi = ["utils/launch_streamlit.sh","utils/launch_streamlit.bat"] \ No newline at end of file +dsi = ["utils/launch_streamlit.sh","utils/launch_streamlit.bat"] From 93a00d2bb0498db94dadd0fd95c69bf51a094a0c Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Fri, 24 Apr 2026 12:33:22 -0600 Subject: [PATCH 009/109] VCS: begin stubbing vcs interface in core. --- dsi/core.py | 38 +++++++++++++++++++++++++++++++- dsi/tests/test_core.py | 50 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/dsi/core.py b/dsi/core.py index 24caa3a4..f46b16ae 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1420,4 +1420,40 @@ def can_create_file_here(self, dir = "."): pass return True except (PermissionError, OSError): - return False \ No newline at end of file + return False + + def vcs_init(self, repo_path): + """Initialize VCS repo." + pass + + def vcs_status(self): + """Return vcs status metadata for the first loaded backend.""" + pass + + def vcs_commit(self, message, author_name=None, author_email=None): + """Commit the current versioned backend state.""" + pass + + def vcs_diff(self, old_ref=None, new_ref=None, cached=False): + """Return the git diff for the first loaded backend.""" + pass + + def vcs_log(self, limit=None, ref='HEAD'): + """Return commit history for the first loaded backend.""" + pass + + def vcs_current_branch(self): + """Return the current branch for the first loaded backend.""" + pass + + def vcs_list_branches(self): + """Return local branches for the first loaded backend.""" + pass + + def vcs_create_branch(self, name, start_point='HEAD', checkout=False, force=False): + """Create a branch on the first loaded backend.""" + pass + + def vcs_checkout_branch(self, name, force=False): + """Checkout a branch on the first loaded backend.""" + pass diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index 982346bb..eb366857 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -20,6 +20,54 @@ def test_unload_module(): a.unload_module('plugin', 'GitInfo', 'writer') assert len(a.list_loaded_modules()['writer']) == 0 +def test_sqlite_versioning(tmp_path): + a = Terminal() + dbpath = tmp_path / "core_versioned.db" + # dbpath = '/tmp/versionined.db' + + a.load_module( + 'plugin', + 'Dict', + 'reader', + collection=OrderedDict({"wildfire": OrderedDict({'foo': [1], 'bar': [3]})}), + ) + a.load_module('backend', 'Sqlite', 'back-write', filename=str(dbpath)) + a.artifact_handler(interaction_type='ingest') + + a.vcs_init(tmp_path) + + # print(a.vcs_current_branch()) + # print(a.vcs_status()) + # status = a.vcs_status() + # assert status['branch'] == 'main' + # assert status['is_dirty'] is True + + # commit_id = a.vcs_commit('initial snapshot') + # assert len(commit_id) == 40 + # assert a.vcs_status()['is_dirty'] is False + # assert a.vcs_log(limit=1)[0]['message'] == 'initial snapshot' + # print(a.vcs_log()) + + # a.vcs_create_branch('feature', checkout=True) + # assert a.vcs_current_branch() == 'feature' + # print(a.vcs_list_branches()) + + # a.update_abstraction('wildfire', OrderedDict({'foo': [1, 2], 'bar': [3, 4]})) + # a.artifact_handler(interaction_type='ingest') + # diff = a.vcs_diff() + # print(diff) + # assert '+INSERT INTO \"wildfire\" VALUES(2,4);' in diff + + # a.vcs_commit('feature snapshot') + + # a.vcs_checkout_branch('main') + # table = a.get_table('wildfire') + # assert table.values.tolist() == [[1, 3]] + # assert sorted(a.vcs_list_branches()) == ['feature', 'main'] + + a.close() + + # SQLITE TESTS def test_ingest_sqlite_backend(): a = Terminal() @@ -980,4 +1028,4 @@ def test_test_sanitize_input(): !jack1 | 2.0 | 3.0 | 45.98 | 3.0 | 44.8 | 0.0099 """) - assert output == expected_output \ No newline at end of file + assert output == expected_output From 10e377ff1ccbcc29ac4a59833f62474d2231d2b0 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 09:44:02 -0600 Subject: [PATCH 010/109] VCS:update diff and modifeid version class --- dsi/core.py | 2 +- dsi/dsi.py | 70 ++++++++++++++++++- dsi/tests/test_dsi.py | 27 ++++++- dsi/utils/version_control/__init__.py | 4 +- dsi/utils/version_control/cli.py | 23 +++--- dsi/utils/version_control/dsi_vcs.py | 51 +++++++++++--- dsi/utils/version_control/tests/test_vcs.py | 28 +++++++- dsi/utils/version_control/vcs_db.py | 2 +- .../version_control/vcs_metadata_helper.py | 53 +++++++------- 9 files changed, 208 insertions(+), 52 deletions(-) diff --git a/dsi/core.py b/dsi/core.py index f46b16ae..e6f51c49 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1423,7 +1423,7 @@ def can_create_file_here(self, dir = "."): return False def vcs_init(self, repo_path): - """Initialize VCS repo." + """Initialize VCS repo.""" pass def vcs_status(self): diff --git a/dsi/dsi.py b/dsi/dsi.py index a6ce27b2..e299f389 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -10,6 +10,8 @@ import ast import warnings + +from dsi.utils.version_control.dsi_vcs import Version warnings.filterwarnings("ignore", category=FutureWarning) logger = logging.getLogger(__name__) @@ -140,7 +142,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) msg = f"Created an instance of DSI with the {backend_name} backend: {filename}" else: msg = "Created an instance of DSI" - + self.vcs = None logger.log(logging.INFO, msg) if self.silence_messages else print(msg) def list_backends(self): @@ -1083,3 +1085,69 @@ def move(self, filepath): def fetch(self, fname): pass + + def version(self, command: str, args: str = None): + """ + Internal DSI Versioning. + + `command` : str + - init # initialize a versioning repository in a root folder + - add # add file(s) to the staging area for the next commit + - remove # remove file(s) from the staging area without touching the actual files + - delete # delete file(s) from the staging area for the next commit + - commit # commit a new version with the staged file(s) and an optional message describing the version + - log # list versions + - diff # diff between two versions. If no version is provided, diff the current version with the previous version + - restore # restore a version with commit hash + + `args` : str + - init: A required argument with the name of the root folder for the versioning repository. + - add: A required argument with the file(s) to add to the staging area for the next commit, specified as a space-separated string or list of file paths. + - remove: A required argument with the file(s) to remove from the staging area without touching the actual files, specified as a space-separated string or list of file paths. + - delete: A required argument with the file(s) to delete from the staging area for the next commit, specified as a space-separated string or list of file paths. + - commit: An optional message describing the version being committed, specified as a string. + - log: An optional argument to specify the number of recent versions to display, specified as an integer. If not provided, recent 5 versions are displayed. + - diff: An optional argument to specify the versions to compare, specified as a space-separated string or list of commit hashes. + - restore: A required argument to specify the version to restore, specified by its commit hash. + """ + print("hello from versioning") + if command == "init": + if args is None: + raise RuntimeError("version() ERROR: 'init' command requires a 'root_folder' argument specifying the name of the root folder for the versioning repository.") + + self.vcs = Version(args) + elif command == "add" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'add' command requires a 'files' argument specifying the file(s) to add to the staging area for the next commit.") + print("-->" + self.vcs.root_folder) + self.vcs.cmd_add(args.split()) + elif command == "remove" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'remove' command requires a 'files' argument specifying the file(s) to remove from the staging area for the next commit.") + self.vcs.cmd_remove(args.split()) + elif command == "delete" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'delete' command requires a 'files' argument specifying the file(s) to delete from the repository.") + self.vcs.cmd_delete(args.split()) + elif command == "commit" and self.vcs is not None: + self.vcs.cmd_commit(args) + elif command == "diff" and self.vcs is not None: + c1 = None + c2 = None + if args is not None: + arg_list = args.split() + if len(arg_list) == 1: + c1 = arg_list[0] + elif len(arg_list) == 2: + c1, c2 = arg_list + elif len(arg_list) > 2: + raise RuntimeError("version() ERROR: 'diff' command requires zero, one, or two commit hashes as arguments.") + self.vcs.cmd_diff(c1, c2) + elif command == "log" and self.vcs is not None: + self.vcs.cmd_log() + elif command == "restore" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'restore' command requires a 'commit_hash' argument specifying the version to restore.") + self.vcs.cmd_restore(args) + else: + raise RuntimeError("version() ERROR: Invalid command or versioning repository not initialized. Please check the command and ensure 'init' has been called with a root folder argument.") \ No newline at end of file diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index cf849b5c..bf12504b 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1796,4 +1796,29 @@ def test_fail_overwrite_schema_duckdb_backend(): assert False except Exception as e: expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." - assert str(e) == expected \ No newline at end of file + assert str(e) == expected + +def main(): +# def test_versioning(): + test = DSI() + # test.version("init", os.getcwd()) + wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + # wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + test.version("add", os.path.join(wpath, "a_dummy_file")) + print(">Single file added.") + test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) + print(">Multi file added.") + print(">Versioning initialized.") + + test.version("remove", os.path.join(wpath, "schema2.json")) + print(">Single file removed.") + + test.version("commit", "Tester Commits") + test.version("log") + assert True + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsi/utils/version_control/__init__.py b/dsi/utils/version_control/__init__.py index 64b89704..d22f487d 100644 --- a/dsi/utils/version_control/__init__.py +++ b/dsi/utils/version_control/__init__.py @@ -1,3 +1,3 @@ -from .dsi_vcs import DSIVCS +from .dsi_vcs import Version -__all__ = ["DSIVCS"] +__all__ = ["Version"] diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py index 97823901..50b801f4 100644 --- a/dsi/utils/version_control/cli.py +++ b/dsi/utils/version_control/cli.py @@ -1,8 +1,9 @@ import argparse import datetime import sys +import os -from .dsi_vcs import DSIVCS +from .dsi_vcs import Version def main(): parser = argparse.ArgumentParser( @@ -28,8 +29,8 @@ def main(): p_log = sub.add_parser("log", help="List all commits") p_diff = sub.add_parser("diff", help="Diff two commits") - p_diff.add_argument("c1") - p_diff.add_argument("c2") + p_diff.add_argument("c1", nargs="?", default=None) + p_diff.add_argument("c2", nargs="?", default=None) p_restore = sub.add_parser("restore", help="Restore a version") p_restore.add_argument("version") @@ -39,27 +40,27 @@ def main(): # parser.print_help() # parser.format_help() if args.command == "init": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) elif args.command == "add": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_add(args.files) elif args.command == "delete": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_delete(args.files) elif args.command == "remove": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_remove(args.files) elif args.command == "commit": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_commit(args.message) elif args.command == "diff": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_diff(args.c1, args.c2) elif args.command == "log": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_log() elif args.command == "restore": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_restore(args.version) if __name__ == "__main__": diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index bc30538a..4c20c1a3 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -91,7 +91,7 @@ def apply_snapshot_deletes(snapshot_path: str, root_folder: str, staged_deletes: # ─────────────────────────── COMMANDS ──────────────────────────────────────── -class DSIVCS(): +class Version(): def __init__(self, folder: str): self.root_folder = os.path.abspath(folder) @@ -411,16 +411,36 @@ def cmd_diff(self, c1: str, c2: str): root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) + def get_files_in_root_folder(): + files = {} + for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in {DB_NAME, SNAPSHOTS_DIR}] + for fname in filenames: + if fname not in {DB_NAME, SNAPSHOTS_DIR}: + abs_path = os.path.join(dirpath, fname) + rel_path = os.path.relpath(abs_path, root_folder) + files[rel_path] = collect_metadata(abs_path, self.root_folder) + files[rel_path]["absolute_path"] = abs_path + files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} + return files + def get_files(chash): - vid = conn.execute( - "SELECT id FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", - (root_folder, chash + "%") - ).fetchone() + vid = None + if chash == "latest": + vid = conn.execute( + "SELECT id, commit_hash FROM versions WHERE root_folder=? ORDER BY id DESC LIMIT 1", + (root_folder,) + ).fetchone() + else: + vid = conn.execute( + "SELECT id, commit_hash FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, chash + "%") + ).fetchone() if not vid: conn.close() sys.exit(f"Commit '{chash}' not found.") rows = conn.execute( - "SELECT relative_path, file_type, md5_hash, permissions_octal, " + "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_octal, " " owner_name, group_name, lstat " "FROM file_entries WHERE version_id=?", (vid["id"],) ).fetchall() @@ -428,12 +448,25 @@ def get_files(chash): for r in rows: rec = dict(r) # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. + snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, vid["commit_hash"][:12]) + rec["absolute_path"] = snapshot_target(snapshot_path, r["relative_path"]) rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} result[r["relative_path"]] = rec return result - files1 = get_files(c1) - files2 = get_files(c2) + files1 = files2 = {} + if c1 == None and c2 == None: + files1 = get_files_in_root_folder() + files2 = get_files("latest") + elif c1 == None: + files1 = get_files_in_root_folder() + files2 = get_files(c2) + elif c2 == None: + files1 = get_files(c1) + files2 = get_files_in_root_folder() + else: + files1 = get_files(c1) + files2 = get_files(c2) conn.close() all_paths = sorted(set(files1) | set(files2)) @@ -461,6 +494,8 @@ def get_files(chash): changes = [] if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: changes.append("content") + result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) + print(f"diff result: {result.stdout.strip()}") if f1["permissions_octal"] != f2["permissions_octal"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index c1b961ba..47a73edb 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -5,7 +5,8 @@ import pytest import stat -from dsi.utils.version_control.dsi_vcs import DSIVCS +from dsi.dsi import DSI +from dsi.utils.version_control.dsi_vcs import Version from dsi.utils.version_control.vcs_db import DB_NAME, SNAPSHOTS_DIR def require_rsync(): @@ -34,7 +35,7 @@ def latest_entries(repo_path): def test_add(tmp_path): require_rsync() - repo = DSIVCS(str(tmp_path)) + repo = Version(str(tmp_path)) empty_dir = tmp_path / "empty" nested_dir = tmp_path / "nested" / "child" @@ -68,3 +69,26 @@ def test_add(tmp_path): assert empty_entry["permissions_str"] == "rwxrwsr-x" assert eval(empty_entry["permissions_octal"]) == 0o2775 assert empty_entry["setgid"] == 1 + + +def test_versioning(): + test = DSI() + # test.version("init", os.getcwd()) + # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + test.version("add", os.path.join(wpath, "a_dummy_file")) + print(">Single file added.") + test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) + print(">Multi file added.") + print(">Versioning initialized.") + + test.version("remove", os.path.join(wpath, "schema2.json")) + print(">Single file removed.") + + test.version("commit", "Tester Commits") + test.version("log") + test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") + assert True diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index aaf21fca..c5e849fa 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS staging ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, - absolute_path TEXT NOT NULL, + absolute_path TEXT NOT NULL UNIQUE, -- absolute path must be unique in staging action TEXT NOT NULL DEFAULT 'add', added_at TEXT NOT NULL, -- ISO-8601 timestamp UNIQUE(root_folder, absolute_path) diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index 9b756f3b..ec39915c 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -76,12 +76,14 @@ def get_acl(path: str) -> Optional[str]: acl_entries.append(acl_part) return ";".join(acl_entries) if acl_entries else None - else: + elif sys.platform == "linux": result = subprocess.run( ["getfacl", "--omit-header", "--absolute-names", path], capture_output=True, text=True, timeout=5 ) return result.stdout.strip() or None + else: + return None except (FileNotFoundError, subprocess.TimeoutExpired): return None @@ -107,29 +109,30 @@ def set_acl(path: str, acl_string: str) -> Optional[str]: def get_xattrs(path: str) -> Optional[str]: - """Return JSON dict of extended attributes, or None.""" - try: - import xattr # pyxattr - attrs = {} - for key in xattr.listxattr(path, symlink=True): - try: - val = xattr.getxattr(path, key, symlink=True) - attrs[key] = val.decode("utf-8", errors="replace") - except OSError: - attrs[key] = "" - return json.dumps(attrs) if attrs else None - except ImportError: - # Fallback: use getfattr - try: - result = subprocess.run( - ["getfattr", "-d", "-m", "-", "--absolute-names", path], - capture_output=True, text=True, timeout=5 - ) - return result.stdout.strip() or None - except FileNotFoundError: - return None - except OSError: - return None + return None + # """Return JSON dict of extended attributes, or None.""" + # try: + # import xattr # pyxattr + # attrs = {} + # for key in xattr.listxattr(path, symlink=True): + # try: + # val = xattr.getxattr(path, key, symlink=True) + # attrs[key] = val.decode("utf-8", errors="replace") + # except OSError: + # attrs[key] = "" + # return json.dumps(attrs) if attrs else None + # except ImportError: + # # Fallback: use getfattr + # try: + # result = subprocess.run( + # ["getfattr", "-d", "-m", "-", "--absolute-names", path], + # capture_output=True, text=True, timeout=5 + # ) + # return result.stdout.strip() or None + # except FileNotFoundError: + # return None + # except OSError: + # return None def get_security_context(path: str) -> Optional[str]: @@ -218,7 +221,7 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: # external / subprocess-derived attributes "acl_text": get_acl(abs_path), - "xattrs": get_xattrs(abs_path), + # "xattrs": get_xattrs(abs_path), "security_context": get_security_context(abs_path), "symlink_target": os.readlink(abs_path) if ftype == "symlink" else None, From 7802959f6edf3a5d5d8543e4dc73fc4ea20f98a1 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 10:52:13 -0600 Subject: [PATCH 011/109] VCS: update versioning tests --- dsi/tests/test_dsi.py | 13 +++++++------ dsi/utils/version_control/tests/test_vcs.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index bf12504b..671d0397 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1798,12 +1798,12 @@ def test_fail_overwrite_schema_duckdb_backend(): expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." assert str(e) == expected -def main(): -# def test_versioning(): +# def main(): +def test_versioning(): test = DSI() # test.version("init", os.getcwd()) - wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - # wpath = os.getcwd() + # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + wpath = os.getcwd() test.version("init", wpath) assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") @@ -1818,7 +1818,8 @@ def main(): test.version("commit", "Tester Commits") test.version("log") + test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") assert True -if __name__ == "__main__": - main() \ No newline at end of file +# if __name__ == "__main__": +# main() \ No newline at end of file diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 47a73edb..6b7ba913 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -90,5 +90,5 @@ def test_versioning(): test.version("commit", "Tester Commits") test.version("log") - test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") + test.version("diff") assert True From 492ebc3c0653b2f3151ab2f4bb38911299abe6d7 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 11:44:34 -0600 Subject: [PATCH 012/109] VCS: add readme file --- dsi/utils/version_control/README.md | 335 ++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 dsi/utils/version_control/README.md diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md new file mode 100644 index 00000000..8b5dd0ea --- /dev/null +++ b/dsi/utils/version_control/README.md @@ -0,0 +1,335 @@ +# dsi-vcs: rsync-based File Version Control with Full Linux Metadata + +## Overview + +**dsi-vcs** is a lightweight file version control system designed for capturing and preserving complete Linux file metadata. Unlike traditional VCS tools, dsi-vcs focuses on: + +- **Full metadata capture**: permissions, ownership, ACLs, extended attributes, and SELinux contexts +- **Rsync-based snapshots**: efficient storage with hard-link deduplication +- **SQLite database**: structured metadata storage for querying and diffing +- **Complete file history**: MD5 hashes, file stats, and all metadata changes tracked + +--- + +## CLI Commands + +Initialize dsi-vcs in the current directory: + +```bash +dsi-vcs init +``` + +### Stage Files for Commit + +**Add files/directories:** + +```bash +dsi-vcs add [ ...] +dsi-vcs add ./data +dsi-vcs add file1.txt file2.txt +``` + +**Stage files for deletion:** + +```bash +dsi-vcs delete [ ...] +dsi-vcs delete ./old_data/ +``` + +**Remove files from staging (without deleting):** + +```bash +dsi-vcs remove [ ...] +dsi-vcs remove file1.txt +``` + +### Commit Changes + +Create a snapshot with an optional message: + +```bash +dsi-vcs commit +dsi-vcs commit "Initial data import" +``` + +### View History + +List all commits: + +```bash +dsi-vcs log +``` + +Example output: + +```shell +COMMIT HASH OWNER DATE/TIME (UTC) FILES BYTES MESSAGE +──────────────────────────────────────────────────────────────────────────────────────────────────── +f826177ae78f4e48a8c08054e2bb9a71 owner1 2026-04-28T21:10:05.339321+00:00 7 15,557 first commit +4af9e3d4dc854d699b96b5a84f913ac0 owner2 2026-04-28T21:19:47.167081+00:00 7 15,559 second commit +``` + +### Compare Versions + +Diff two commits (shows added, modified, deleted files). + +Parameters: + +- Provide upto two commit hash as paramerter. +- No parameter will compare with the latest changes with the last commit. +- One parameter will compare with the latest change with the provided commit version. +- Two parameters will compare between the provided versions. + +```bash +dsi-vcs diff +dsi-vcs diff f826177ae78f4e48a8c08054e2bb9a71 +``` + +Example output: + +```shell +Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) + +STATUS PATH +────────────────────────────────────────────────────────────────────── +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + +Summary: +0 added -0 deleted ~3 modified =4 unchanged +``` + +### Restore a Version + +Restore the entire repository to a previous commit: + +```bash +dsi-vcs restore +dsi-vcs restore abc123def456 +``` + +## Python API + +Integrated within DSI. User versioning with the function `version(command, args)` + +### Usage + +```python +obj.version(command: str, args: str = None) +``` + +### Parameters + +| Parameter | Type | Description | +| ----------- | ------- | -------------------------------------------------------- | +| `command` | `str` | The versioning operation to perform (see commands below) | +| `args` | `str` | Optional or required arguments depending on the command | + +--- + +### `init` + +Initializes a versioning repository in a root folder. + +```python +obj.version("init", "my_project_folder") +``` + +**Args (required):** Name of the root folder for the versioning repository. + +--- + +### `add` + +Adds one or more files to the staging area for the next commit. + +```python +obj.version("add", "file1.py file2.py") +``` + +**Args (required):** Space-separated file paths to stage. + +--- + +### `remove` + +Removes one or more files from the staging area **without** deleting the actual files. + +```python +obj.version("remove", "file1.py") +``` + +**Args (required):** Space-separated file paths to unstage. + +--- + +### `delete` + +Marks one or more files for deletion in the next commit. + +```python +obj.version("delete", "file1.py file2.py") +``` + +**Args (required):** Space-separated file paths to delete. + +--- + +### `commit` + +Commits all staged changes as a new version snapshot. + +```python +obj.version("commit", "Initial release") # with message +obj.version("commit") # without message +``` + +**Args (optional):** A descriptive message for the version being committed. + +--- + +### `log` + +Lists recent committed versions. + +```python +obj.version("log") # shows last 5 versions (default) +obj.version("log", "10") # shows last 10 versions +``` + +**Args (optional):** Number of recent versions to display. Defaults to `5`. + +--- + +### `diff` + +Shows the differences between two versions. + +```python +obj.version("diff") # current vs previous +obj.version("diff", "abc123") # specific commit vs its previous +obj.version("diff", "abc123 def456") # between two specific commits +``` + +**Args (optional):** Zero, one, or two commit hashes separated by a space. + +| # of hashes | Behavior | +| ----------- | -------------------------------------------------- | +| 0 | Diffs current version against previous | +| 1 | Diffs specified commit against its previous | +| 2 | Diffs the two specified commits against each other | + +--- + +### `restore` + +Restores the repository to a previously committed version. + +```python +obj.version("restore", "abc123def456") +``` + +**Args (required):** The commit hash of the version to restore. + +Example : Basic Workflow + +```python +from dsi.utils.version_control import Version + +# Initialize dsi +dsi = DSI() + +# Initialize repo +dsi.version("init", "/data/archive") + +# Stage files +dsi.version("add", ["./documents", "config.json"]) + +# Commit +dsi.version("commit", "Initial archive") + +# View history +dsi.version("log") + +# Modify files and commit again +dsi.version(["./documents"]) +dsi.version("commit", "Updated documents") + +# Compare two versions +dsi.version("diff") +``` + +## Database Schema + +### `versions` Table + +| Column | Type | Description | +| ------------- | ---------- | --------------------------- | +| id | INTEGER PK | Auto-increment ID | +| root_folder | TEXT | Repository root path | +| commit_hash | TEXT | UUID4 hex (32 chars) | +| committed_at | TEXT | ISO-8601 timestamp | +| owner_name | TEXT | Linux user who committed | +| message | TEXT | Optional commit message | +| snapshot_path | TEXT | Path to rsync snapshot copy | +| file_count | INTEGER | Number of files in commit | +| total_bytes | INTEGER | Total size in bytes | + +### `file_entries` Table + +Stores metadata for each file in each commit. + +| Column | Type | Description | +| ---------------------- | ---------- | -------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| absolute_path | TEXT | Full path | +| file_name | TEXT | Filename only | +| file_type | TEXT | file/dir/symlink/etc | +| md5_hash | TEXT | Content hash (files only) | +| lstat | TEXT | JSON of os.lstat() result | +| permissions_octal | TEXT | e.g., "0o755" | +| permissions_str | TEXT | e.g., "rwxr-xr-x" | +| owner_name | TEXT | Username | +| group_name | TEXT | Group name | +| setuid, setgid, sticky | INTEGER | Special bits (0/1) | +| acl_text | TEXT | Raw getfacl output | +| xattrs | TEXT | JSON dict of extended attributes | +| security_context | TEXT | SELinux context | +| symlink_target | TEXT | Target of symlink | + +### `staging` Table + +Temporary storage for files to be committed. + +| Column | Type | Description | +| ------------- | ---------- | ----------------------- | +| id | INTEGER PK | Auto-increment | +| root_folder | TEXT | Partition key | +| absolute_path | TEXT | Full file path (UNIQUE) | +| action | TEXT | "add" or "delete" | +| added_at | TEXT | ISO-8601 timestamp | + +--- + +## Directory Structure + +``` +root_folder/ +└── .dsi_vcs_snapshots/ # rsync snapshot directory + ├── .dsi_vcs.db # SQLite metadata database + ├── abc123def456/ # Snapshot for commit abc123def456 + ├── xyz789abc123/ # Snapshot for commit xyz789abc123 + └── ... +``` From f6ded58bee6aeab761e31c68d6c63a9742b3d347 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 11:59:34 -0600 Subject: [PATCH 013/109] VCS: fix linter errors --- dsi/utils/version_control/cli.py | 4 +-- dsi/utils/version_control/dsi_vcs.py | 11 +++--- .../version_control/vcs_metadata_helper.py | 34 ++++++++++++------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py index 50b801f4..06007da2 100644 --- a/dsi/utils/version_control/cli.py +++ b/dsi/utils/version_control/cli.py @@ -11,7 +11,7 @@ def main(): ) sub = parser.add_subparsers(dest="command", required=True) - p_init = sub.add_parser("init", help="Initialize a new repo") + sub.add_parser("init", help="Initialize a new repo") # p_init.add_argument("root_folder") p_add = sub.add_parser("add", help="add file or directory to staging") @@ -26,7 +26,7 @@ def main(): p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) - p_log = sub.add_parser("log", help="List all commits") + sub.add_parser("log", help="List all commits") p_diff = sub.add_parser("diff", help="Diff two commits") p_diff.add_argument("c1", nargs="?", default=None) diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 4c20c1a3..67ea3965 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -27,11 +27,10 @@ import json import datetime import shutil -from pathlib import Path from typing import Optional -from .vcs_db import * -from .vcs_metadata_helper import * +from .vcs_db import DB_NAME, SNAPSHOTS_DIR, open_db +from .vcs_metadata_helper import collect_metadata, collect_tree_metadata, owner_name # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -455,13 +454,13 @@ def get_files(chash): return result files1 = files2 = {} - if c1 == None and c2 == None: + if c1 is None and c2 is None: files1 = get_files_in_root_folder() files2 = get_files("latest") - elif c1 == None: + elif c1 is None: files1 = get_files_in_root_folder() files2 = get_files(c2) - elif c2 == None: + elif c2 is None: files1 = get_files(c1) files2 = get_files_in_root_folder() else: diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index ec39915c..8bc4da7c 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -1,7 +1,6 @@ import os import stat import hashlib -import sqlite3 import json import subprocess import pwd @@ -14,13 +13,20 @@ # ─────────────────────────── METADATA HELPERS ──────────────────────────────── def file_type_str(mode: int) -> str: - if stat.S_ISREG(mode): return "file" - if stat.S_ISDIR(mode): return "dir" - if stat.S_ISLNK(mode): return "symlink" - if stat.S_ISBLK(mode): return "block" - if stat.S_ISCHR(mode): return "char" - if stat.S_ISFIFO(mode): return "fifo" - if stat.S_ISSOCK(mode): return "socket" + if stat.S_ISREG(mode): + return "file" + if stat.S_ISDIR(mode): + return "dir" + if stat.S_ISLNK(mode): + return "symlink" + if stat.S_ISBLK(mode): + return "block" + if stat.S_ISCHR(mode): + return "char" + if stat.S_ISFIFO(mode): + return "fifo" + if stat.S_ISSOCK(mode): + return "socket" return "unknown" @@ -44,13 +50,17 @@ def permission_str(mode: int) -> str: def owner_name(uid: int) -> str: - try: return pwd.getpwuid(uid).pw_name - except KeyError: return str(uid) + try: + return pwd.getpwuid(uid).pw_name + except KeyError: + return str(uid) def group_name(gid: int) -> str: - try: return grp.getgrgid(gid).gr_name - except KeyError: return str(gid) + try: + return grp.getgrgid(gid).gr_name + except KeyError: + return str(gid) def get_acl(path: str) -> Optional[str]: From 495904f3358df92591fef91596241e7073c12669 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 12:13:30 -0600 Subject: [PATCH 014/109] VCS: fix version testers --- dsi/tests/test_core.py | 48 ------------------------------------------ dsi/tests/test_dsi.py | 27 ++++++++++++++++++------ 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index 10391fec..9b5ef95f 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -21,54 +21,6 @@ def test_unload_module(): a.unload_module('plugin', 'GitInfo', 'writer') assert len(a.list_loaded_modules()['writer']) == 0 -def test_sqlite_versioning(tmp_path): - a = Terminal() - dbpath = tmp_path / "core_versioned.db" - # dbpath = '/tmp/versionined.db' - - a.load_module( - 'plugin', - 'Dict', - 'reader', - collection=OrderedDict({"wildfire": OrderedDict({'foo': [1], 'bar': [3]})}), - ) - a.load_module('backend', 'Sqlite', 'back-write', filename=str(dbpath)) - a.artifact_handler(interaction_type='ingest') - - a.vcs_init(tmp_path) - - # print(a.vcs_current_branch()) - # print(a.vcs_status()) - # status = a.vcs_status() - # assert status['branch'] == 'main' - # assert status['is_dirty'] is True - - # commit_id = a.vcs_commit('initial snapshot') - # assert len(commit_id) == 40 - # assert a.vcs_status()['is_dirty'] is False - # assert a.vcs_log(limit=1)[0]['message'] == 'initial snapshot' - # print(a.vcs_log()) - - # a.vcs_create_branch('feature', checkout=True) - # assert a.vcs_current_branch() == 'feature' - # print(a.vcs_list_branches()) - - # a.update_abstraction('wildfire', OrderedDict({'foo': [1, 2], 'bar': [3, 4]})) - # a.artifact_handler(interaction_type='ingest') - # diff = a.vcs_diff() - # print(diff) - # assert '+INSERT INTO \"wildfire\" VALUES(2,4);' in diff - - # a.vcs_commit('feature snapshot') - - # a.vcs_checkout_branch('main') - # table = a.get_table('wildfire') - # assert table.values.tolist() == [[1, 3]] - # assert sorted(a.vcs_list_branches()) == ['feature', 'main'] - - a.close() - - # SQLITE TESTS def test_ingest_sqlite_backend(): a = Terminal() diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index d4486757..fb7ec0d7 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -6,6 +6,8 @@ from pandas import DataFrame from collections import OrderedDict import hashlib +import random +import tempfile def test_list_functions(): test = DSI() @@ -1804,14 +1806,27 @@ def test_versioning(): test.version("init", wpath) assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + # Create a file with ten random integers + dummy_file_path = os.path.join(wpath, "a_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') test.version("add", os.path.join(wpath, "a_dummy_file")) - print(">Single file added.") - test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) - print(">Multi file added.") - print(">Versioning initialized.") - test.version("remove", os.path.join(wpath, "schema2.json")) - print(">Single file removed.") + dummy_file_path = os.path.join(wpath, "second_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "second_dummy_file")) + + dummy_file_path = os.path.join(wpath, "third_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "third_dummy_file")) + + test.version("remove", os.path.join(wpath, "second_dummy_file")) + test.version("delete", os.path.join(wpath, "third_dummy_file")) test.version("commit", "Tester Commits") test.version("log") From 80ad9c04e512f50f1d0e78caff0ea9edadee0eb0 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 12:15:02 -0600 Subject: [PATCH 015/109] VCS: remvoed unused import --- dsi/tests/test_dsi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index fb7ec0d7..c9b56bba 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -7,7 +7,6 @@ from collections import OrderedDict import hashlib import random -import tempfile def test_list_functions(): test = DSI() From 9ed208e6a4ebadc2a673ff9adb938c56b96f57ab Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 14 May 2026 10:33:58 -0600 Subject: [PATCH 016/109] VCS: readme user text update --- dsi/dsi.py | 1 - dsi/utils/version_control/README.md | 32 ++++++++++++++--------------- dsi/utils/version_control/vcs_db.py | 2 +- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index f32316ee..de7056f6 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -1284,7 +1284,6 @@ def version(self, command: str, args: str = None): - diff: An optional argument to specify the versions to compare, specified as a space-separated string or list of commit hashes. - restore: A required argument to specify the version to restore, specified by its commit hash. """ - print("hello from versioning") if command == "init": if args is None: raise RuntimeError("version() ERROR: 'init' command requires a 'root_folder' argument specifying the name of the root folder for the versioning repository.") diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index 8b5dd0ea..aa5ec8eb 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -89,22 +89,22 @@ Example output: ```shell Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) - -STATUS PATH + +STATUS PATH ────────────────────────────────────────────────────────────────────── -MODIFIED file_new [owner] -MODIFIED file_schema.json [owner] -diff result: 2c2 -< "genesis_datacard": { ---- -> 2"genesis_datacard": { -26c26 -< } -\ No newline at end of file ---- -> } -MODIFIED schema2.json [content, size] - +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + Summary: +0 added -0 deleted ~3 modified =4 unchanged ``` @@ -278,7 +278,7 @@ dsi.version("diff") | root_folder | TEXT | Repository root path | | commit_hash | TEXT | UUID4 hex (32 chars) | | committed_at | TEXT | ISO-8601 timestamp | -| owner_name | TEXT | Linux user who committed | +| owner_name | TEXT | Username of the committer | | message | TEXT | Optional commit message | | snapshot_path | TEXT | Path to rsync snapshot copy | | file_count | INTEGER | Number of files in commit | diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index c5e849fa..c7bed45c 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -14,7 +14,7 @@ root_folder TEXT NOT NULL, commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex committed_at TEXT NOT NULL, -- ISO-8601 timestamp - owner_name TEXT NOT NULL, -- Linux user who ran the commit + owner_name TEXT NOT NULL, -- Username of the committer message TEXT, snapshot_path TEXT NOT NULL, -- path to rsync copy file_count INTEGER NOT NULL, From 8093eeae8c5121b889712d8364e6e5aa841c0c44 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 21 May 2026 10:45:55 -0600 Subject: [PATCH 017/109] VCS: remove redundant table columns --- dsi/utils/version_control/README.md | 68 +++++++++---------- dsi/utils/version_control/dsi_vcs.py | 7 +- dsi/utils/version_control/tests/test_vcs.py | 29 +------- dsi/utils/version_control/vcs_db.py | 8 +-- .../version_control/vcs_metadata_helper.py | 9 +-- 5 files changed, 40 insertions(+), 81 deletions(-) diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index aa5ec8eb..23e6c85f 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -89,22 +89,22 @@ Example output: ```shell Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) - -STATUS PATH + +STATUS PATH ────────────────────────────────────────────────────────────────────── -MODIFIED file_new [owner] -MODIFIED file_schema.json [owner] -diff result: 2c2 -< "genesis_datacard": { ---- -> 2"genesis_datacard": { -26c26 -< } -\ No newline at end of file ---- -> } -MODIFIED schema2.json [content, size] - +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + Summary: +0 added -0 deleted ~3 modified =4 unchanged ``` @@ -288,26 +288,24 @@ dsi.version("diff") Stores metadata for each file in each commit. -| Column | Type | Description | -| ---------------------- | ---------- | -------------------------------- | -| id | INTEGER PK | Auto-increment | -| version_id | INTEGER FK | References versions(id) | -| root_folder | TEXT | Partition key | -| relative_path | TEXT | Path relative to root | -| absolute_path | TEXT | Full path | -| file_name | TEXT | Filename only | -| file_type | TEXT | file/dir/symlink/etc | -| md5_hash | TEXT | Content hash (files only) | -| lstat | TEXT | JSON of os.lstat() result | -| permissions_octal | TEXT | e.g., "0o755" | -| permissions_str | TEXT | e.g., "rwxr-xr-x" | -| owner_name | TEXT | Username | -| group_name | TEXT | Group name | -| setuid, setgid, sticky | INTEGER | Special bits (0/1) | -| acl_text | TEXT | Raw getfacl output | -| xattrs | TEXT | JSON dict of extended attributes | -| security_context | TEXT | SELinux context | -| symlink_target | TEXT | Target of symlink | +| Column | Type | Description | +| ---------------- | ---------- | -------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| absolute_path | TEXT | Full path | +| file_name | TEXT | Filename only | +| file_type | TEXT | file/dir/symlink/etc | +| md5_hash | TEXT | Content hash (files only) | +| lstat | TEXT | JSON of os.lstat() result | +| permissions_int | INTEGER | e.g. 755 | +| owner_name | TEXT | Username | +| group_name | TEXT | Group name | +| acl_text | TEXT | Raw getfacl output | +| xattrs | TEXT | JSON dict of extended attributes | +| security_context | TEXT | SELinux context | +| symlink_target | TEXT | Target of symlink | ### `staging` Table diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 67ea3965..15eede3e 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -351,8 +351,7 @@ def cmd_commit(self, message: str = ""): "version_id", "root_folder", "relative_path", "absolute_path", "file_name", "file_type", "md5_hash", "lstat", - "permissions_octal", "permissions_str", "owner_name", "group_name", - "setuid", "setgid", "sticky", + "permissions_int", "owner_name", "group_name", "acl_text", "xattrs", "security_context", "symlink_target", ] placeholders = ",".join("?" * len(cols)) @@ -439,7 +438,7 @@ def get_files(chash): conn.close() sys.exit(f"Commit '{chash}' not found.") rows = conn.execute( - "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_octal, " + "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_int, " " owner_name, group_name, lstat " "FROM file_entries WHERE version_id=?", (vid["id"],) ).fetchall() @@ -495,7 +494,7 @@ def get_files(chash): changes.append("content") result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) print(f"diff result: {result.stdout.strip()}") - if f1["permissions_octal"] != f2["permissions_octal"]: + if f1["permissions_int"] != f2["permissions_int"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: changes.append("owner") diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 6b7ba913..6a13c930 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -29,7 +29,7 @@ def latest_entries(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row return conn.execute( - "SELECT relative_path, file_type, permissions_octal, permissions_str, setgid " + "SELECT relative_path, file_type, permissions_int " "FROM file_entries WHERE version_id=(SELECT MAX(id) from versions) " "ORDER BY relative_path").fetchall() @@ -66,29 +66,4 @@ def test_add(tmp_path): rows = {row["relative_path"]: row for row in latest_entries(tmp_path)} empty_entry = rows["empty"] assert empty_entry["file_type"] == "dir" - assert empty_entry["permissions_str"] == "rwxrwsr-x" - assert eval(empty_entry["permissions_octal"]) == 0o2775 - assert empty_entry["setgid"] == 1 - - -def test_versioning(): - test = DSI() - # test.version("init", os.getcwd()) - # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - wpath = os.getcwd() - test.version("init", wpath) - assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") - - test.version("add", os.path.join(wpath, "a_dummy_file")) - print(">Single file added.") - test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) - print(">Multi file added.") - print(">Versioning initialized.") - - test.version("remove", os.path.join(wpath, "schema2.json")) - print(">Single file removed.") - - test.version("commit", "Tester Commits") - test.version("log") - test.version("diff") - assert True + assert empty_entry["permissions_int"] == 0o2775 diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index c7bed45c..989ce0da 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -42,16 +42,10 @@ lstat TEXT NOT NULL, -- ── Human-readable permission strings ───────────────────────────────── - permissions_octal TEXT, -- e.g. "0o755" - permissions_str TEXT, -- e.g. "rwxr-xr-x" + permissions_int INTEGER, -- e.g. "755" as an integer owner_name TEXT, group_name TEXT, - -- ── Special bits ────────────────────────────────────────────────────── - setuid INTEGER DEFAULT 0, - setgid INTEGER DEFAULT 0, - sticky INTEGER DEFAULT 0, - -- ── ACL (POSIX) ─────────────────────────────────────────────────────── acl_text TEXT, -- raw getfacl output diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index 8bc4da7c..6bf4a417 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -39,7 +39,6 @@ def permission_str(mode: int) -> str: (stat.S_IROTH, "r"), (stat.S_IWOTH, "w"), (stat.S_IXOTH, "x"), ): chars.append(char if mode & bit else "-") - # Replace x with s/t for setuid/setgid/sticky if mode & stat.S_ISUID: chars[2] = "s" if chars[2] == "x" else "S" if mode & stat.S_ISGID: @@ -219,16 +218,10 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: "lstat": json.dumps(lstat_dict), # human-readable permission strings - "permissions_octal": oct(stat.S_IMODE(mode)), - "permissions_str": permission_str(mode), + "permissions_int": stat.S_IMODE(mode), "owner_name": owner_name(s.st_uid), "group_name": group_name(s.st_gid), - # special-bit flags - "setuid": int(bool(mode & stat.S_ISUID)), - "setgid": int(bool(mode & stat.S_ISGID)), - "sticky": int(bool(mode & stat.S_ISVTX)), - # external / subprocess-derived attributes "acl_text": get_acl(abs_path), # "xattrs": get_xattrs(abs_path), From 6a2741e121d099ab6a73a73769406d9d49c421cd Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Tue, 24 Mar 2026 08:52:10 -0600 Subject: [PATCH 018/109] Stylized parallel coordinate plot --- tools/perf_analyzer/fly_server.py | 149 ++++++++++++++++----------- tools/perf_analyzer/runner_script.sh | 6 +- 2 files changed, 91 insertions(+), 64 deletions(-) diff --git a/tools/perf_analyzer/fly_server.py b/tools/perf_analyzer/fly_server.py index e3465720..717b0452 100755 --- a/tools/perf_analyzer/fly_server.py +++ b/tools/perf_analyzer/fly_server.py @@ -353,16 +353,24 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) combined_all_df = merged_df[merged_df.cname != None].sort_values(by=['date'], ascending=True) combined_all_df["formatted_date"] = pd.to_datetime(combined_all_df['date']).dt.strftime("%b-%d,%Y(%H:%M:%S)") - combined_all_df = combined_all_df.dropna(subset=perf_filter.extend(["date"])).reset_index(drop=True) + combined_all_df = combined_all_df\ + .dropna(subset=perf_filter.extend(["date"])).reset_index(drop=True)\ + .drop_duplicates(subset=['formatted_date'], keep='first') + + # dims = [] + # labels = [] - # print(combined_all_df["formatted_date"]) + print(combined_all_df["formatted_date"]) + # print(combined_all_df["branch"]) parCordValues = list() - if sorted_df is not None and len(sorted_df) > 0: + if combined_all_df is not None and len(combined_all_df) > 0: f_col = combined_all_df.index.values + # labels.append("Commit
Time") + # dims.append(f_col) parCordValues.append( dict(range = [f_col.min(), f_col.max()], values = f_col, - label = "Commit
Time", tickvals = f_col, ticktext = combined_all_df["formatted_date"].astype(str)) + label = "
Commit
Time", tickvals = f_col, ticktext = combined_all_df["formatted_date"].astype(str)) ) for col_name in perf_filter:# ["pdv", "cell_advection", "mpi_halo_exchange", "self_halo_exchange", "momentum_advection", "total"]: if col_name == "date": @@ -371,7 +379,7 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) # Replace the second to last underscore with a white space in the label parts = col_name.split('_') csum = 0 - label = '' + label = '
' for part in parts: if csum > 0: label += '_' @@ -380,11 +388,13 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) if csum > len(col_name) // 2: label += "
" csum = 0 + # labels.append(label) + # dims.append(f_col) parCordValues.append( dict(range = [f_col.min(), f_col.max()], label = label, values = f_col) ) - + # if len(dims) == 0: if len(parCordValues) == 0: fig = go.Figure().add_annotation( x=2, y=2, @@ -395,10 +405,11 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) fig.update_layout( xaxis = { "visible": False }, yaxis = { "visible": False }) + # return fig else: fig = go.Figure(data=go.Parcoords( line = dict(color = parCordValues[0]['values'], - colorscale = 'Electric', + colorscale = [[0, 'blue'], [1, 'blue']], showscale = False, colorbar = dict(x=-0.15, showticklabels=False), cmin = parCordValues[0]['range'][0], @@ -406,9 +417,12 @@ def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()) dimensions = parCordValues, labelangle = 0, labelside = "bottom", + labelfont = dict(size=16, color='black'), + rangefont = dict(size=14, color='black'), + tickfont = dict(size=12, color='black'), ) ) - fig.update_layout(margin=dict(l=150)) + fig.update_layout(margin=dict(l=160)) return fig # @callback( @@ -546,68 +560,75 @@ def main(perf_data, git_nodes): # 'textOverflow': 'ellipsis', } ), - ], style={'width': 790,'marginTop': 0,'marginLeft': 0}) + ], style={'width': '100%','marginTop': 0,'marginLeft': 0}), # html.Div( # html.Img(src='assets/image.svg', # style={'marginLeft': 15, 'marginRight': 15, 'marginTop': 30, 'width': 310}) # ) + # ], style={ + # 'width': '60%', + # 'height': '1000px', + # 'marginLeft': 10, + # 'marginTop': 5, + # 'marginRight': 10 + # }), + html.Div([ + html.Div([ + html.H4("Search any variable, (regex or plaintext):"), + dcc.Input(id='custom-var-search', value='pragma, define', type='text', style={'marginBottom': 10}), + html.Div(id='search-var-info', children='Choose files types to filter'), + dcc.Dropdown( + options={r"\.c":".c", r"\.cc": ".cc", r"\.py":".py", r"\.f90":".f90", r"\.ipynb": ".ipynb"}, + value = [r"\.c", r"\.cc",], + id="filter-file-multi-options", + clearable=True, + optionHeight=40, + multi=True + ), + html.Div(id='file-filter-selection-text', children='Filtered Files', style={'marginBottom': 10}), + html.Button('Search', id='submit-var-search', n_clicks=0, disabled=True), + dash_table.DataTable(id='var-search-table', + columns=[ + {'name': 'variable', 'id': 'var_name', 'type': 'text'}, + {'name': 'file name', 'id': 'file_name', 'type': 'text'}, + # {'name': 'occurance', 'id': 'occ', 'type': 'numeric'} + ], + filter_action='native', + editable=False, + sort_action="native", + row_selectable="multi", + row_deletable=True, + style_cell={'textAlign': 'left'}, + style_table={ + 'height': '250px', 'minHeight': '200px', 'maxHeight': '250px', + 'overflow': 'auto', + 'font-size': '12px', + }, + style_data={ + # 'width': '100px', 'minWidth': '100px', + 'maxWidth': '100px', + 'whiteSpace': 'normal', + 'overflow': 'auto', + # 'textOverflow': 'ellipsis', + } + ), + # html.Div(id='var-results-table', style={'marginTop': 10}), + html.Div(id='code-view', style={'marginTop': 10, + 'height': '1000px', 'maxHeight': '1000px', + 'overflow': 'auto', + }), + ], style={'marginLeft': 0, 'marginTop': 5}), + ], style={ + 'width': '100%', + 'marginLeft': 0, + }), ], style={ - 'width': '60%', + 'width': '100%', 'height': '1000px', 'marginLeft': 10, 'marginTop': 5, 'marginRight': 10 }), - html.Div([ - html.Div([ - html.H4("Search any variable, (regex or plaintext):"), - dcc.Input(id='custom-var-search', value='pragma, define', type='text', style={'marginBottom': 10}), - html.Div(id='search-var-info', children='Choose files types to filter'), - dcc.Dropdown( - options={r"\.c":".c", r"\.cc": ".cc", r"\.py":".py", r"\.f90":".f90", r"\.ipynb": ".ipynb"}, - value = [r"\.c", r"\.cc",], - id="filter-file-multi-options", - clearable=True, - optionHeight=40, - multi=True - ), - html.Div(id='file-filter-selection-text', children='Filtered Files', style={'marginBottom': 10}), - html.Button('Search', id='submit-var-search', n_clicks=0, disabled=True), - dash_table.DataTable(id='var-search-table', - columns=[ - {'name': 'variable', 'id': 'var_name', 'type': 'text'}, - {'name': 'file name', 'id': 'file_name', 'type': 'text'}, - # {'name': 'occurance', 'id': 'occ', 'type': 'numeric'} - ], - filter_action='native', - editable=False, - sort_action="native", - row_selectable="multi", - row_deletable=True, - style_cell={'textAlign': 'left'}, - style_table={ - 'height': '250px', 'minHeight': '200px', 'maxHeight': '250px', - 'overflow': 'auto', - 'font-size': '12px', - }, - style_data={ - # 'width': '100px', 'minWidth': '100px', - 'maxWidth': '100px', - 'whiteSpace': 'normal', - 'overflow': 'auto', - # 'textOverflow': 'ellipsis', - } - ), - # html.Div(id='var-results-table', style={'marginTop': 10}), - html.Div(id='code-view', style={'marginTop': 10, - 'height': '1000px', 'maxHeight': '1000px', - 'overflow': 'auto', - }), - ], style={'marginLeft': 0, 'marginTop': 5}), - ], style={ - 'width': '40%', - 'marginLeft': 0, - }), ], fluid=True, style={'display': 'flex'}, @@ -744,12 +765,18 @@ def action_on_selected_vars(rows, derived_virtual_selected_rows, selected_commit return mk_component elif len(derived_selected_commits_list) > 2: return dcc.Markdown("##### Please select upto two commits", id='actual-source-block') + elif len(derived_selected_commits_list) == 1: + return dcc.Markdown("##### Please select upto two commits", id='actual-source-block') if derived_virtual_selected_rows is not None and len(derived_virtual_selected_rows) > 0 and len(rows) >= derived_virtual_selected_rows[0]: mk_data = list() for i in range(len(derived_virtual_selected_rows)): mk_data.append(rows[derived_virtual_selected_rows[i]]['file_name']) + # if len(derived_selected_commits_list) > 0 and derived_selected_commits_list[0] not in selected_commits_row: + # return dcc.Markdown("##### Please select two commits to compare", id='actual-source-block') + # if len(derived_selected_commits_list) > 1 and derived_selected_commits_list[1] not in selected_commits_row: + # return dcc.Markdown("##### Please select two commits to compare", id='actual-source-block') git_repo = getGitRepo(perf_runner.git_user_repo) first_commit_hash = selected_commits_row[derived_selected_commits_list[0]]["long_hash"] second_commit_hash = selected_commits_row[derived_selected_commits_list[1]]["long_hash"] @@ -835,7 +862,7 @@ def find_interesting_perf_metric(df): if column == 'testname' or column.startswith("git_") or 'min' in column or 'max' in column or 'x_cells' in column or 'y_cells' in column: continue df[column] = pd.to_numeric(df[column].fillna(value=np.nan), errors='coerce') - another = df.select_dtypes(include='number').std() > 0.015 + another = df.select_dtypes(include='number').std() > 0.03# 0.015 return list(another[another].index) @callback( diff --git a/tools/perf_analyzer/runner_script.sh b/tools/perf_analyzer/runner_script.sh index 61d3b52b..2a63a90f 100644 --- a/tools/perf_analyzer/runner_script.sh +++ b/tools/perf_analyzer/runner_script.sh @@ -19,9 +19,9 @@ cp ../clover.in . make clean; make COMPILER=GNU; -echo "================================ Compile Done ================================ "l - -echo "============================= Running CloverLeaf ============================= "j +echo "================================ Compile Done ================================ " + +echo "============================= Running CloverLeaf ============================= " mpirun -np 2 tau_exec $SOURCE_BASE_DIRECTORY/clover_leaf pprof > tau_results cd - From 255c67c261b3f3c01e38b741ee387b16a0fffc54 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 26 Mar 2026 09:48:29 -0600 Subject: [PATCH 019/109] Version control first demo --- tools/version_control/dsi_vcs.py | 516 +++++++++++++++++++ tools/version_control/requirements.txt | 1 + tools/version_control/vcs_db.py | 94 ++++ tools/version_control/vcs_metadata_helper.py | 248 +++++++++ 4 files changed, 859 insertions(+) create mode 100755 tools/version_control/dsi_vcs.py create mode 100644 tools/version_control/requirements.txt create mode 100644 tools/version_control/vcs_db.py create mode 100644 tools/version_control/vcs_metadata_helper.py diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py new file mode 100755 index 00000000..44ebaa56 --- /dev/null +++ b/tools/version_control/dsi_vcs.py @@ -0,0 +1,516 @@ +#!/usr/bin/env python3 +""" +dsi_vcs.py — rsync-based file version control system +Captures full Linux file metadata (stat, ACL, xattrs), MD5 hash, +and stores versioned snapshots in SQLite. + +Usage: + python dsi_vcs.py init # init repo + python dsi_vcs.py commit [message] # commit a new version + python dsi_vcs.py log # list versions + python dsi_vcs.py diff # diff two versions + python dsi_vcs.py restore # restore a version + +Requirements: + pip install pyxattr # for extended attributes + sudo apt install acl # for getfacl (ACL support) + rsync must be installed # for snapshot copies +""" + +import os +import sys +import uuid +import subprocess +import json +import datetime +import argparse +from pathlib import Path +from typing import Optional + +from vcs_db import * +from vcs_metadata_helper import * + +# ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── + +def rsync_snapshot( + root_folder: str, + dest_path: str, + prev_snapshot: Optional[str] = None, + rel_paths: Optional[list[str]] = None, +) -> bool: + """ + Copy files from root_folder → dest_path using rsync hard-link deduplication. + If rel_paths is given, only those paths are synced (via --files-from). + If prev_snapshot is provided, unchanged files are hard-linked (saves disk). + """ + import tempfile + + os.makedirs(dest_path, exist_ok=True) + cmd = ["rsync", "-aAXH"] # for linux + if sys.platform == "darwin": + cmd = ["rsync", "-aEH"] # for macOS + + if prev_snapshot and os.path.isdir(prev_snapshot): + cmd += ["--link-dest", os.path.abspath(prev_snapshot)] + + # Write a temp file-list when only staging specific paths + files_from_tmp = None + if rel_paths is not None: + tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) + tmp.write("\n".join(rel_paths)) + tmp.close() + files_from_tmp = tmp.name + cmd += ["--files-from", files_from_tmp] + else: + cmd += [ + "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + ] + + cmd += [ + root_folder.rstrip("/") + "/", + dest_path.rstrip("/") + "/", + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): # 24 = some files vanished (ok) + print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) + return False + if sys.platform == "darwin": + for rel_path in rel_paths: + set_acl(dest_path, get_acl(rel_path) or "") + return True + finally: + if files_from_tmp: + os.unlink(files_from_tmp) + + +# ─────────────────────────── COMMANDS ──────────────────────────────────────── +class DSIVCS(): + + def __init__(self, folder: str): + self.root_folder = os.path.abspath(folder) + if not os.path.isdir(self.root_folder): + sys.exit(f"Error: '{self.root_folder}' is not a directory.") + + conn = open_db(self.root_folder) + conn.close() + print(f"Initialized dsi-vcs repository in: {self.root_folder}") + print(f" Snapshots: {self.root_folder}/{SNAPSHOTS_DIR}/") + + + def cmd_add(self, paths: list[str]): + """ + Stage one or more files/directories for the next commit. + Directories are expanded recursively; each resolved file is inserted into + the staging table. Adding an already-staged path is a silent no-op. + paths should be a list of relative paths to root_folder. + """ + # print(f"Staging {len(paths)} path(s) for commit…") + # print(paths) + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + added_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + staged = 0 + skip_names = {DB_NAME, SNAPSHOTS_DIR} + + def stage_path(abs_path: str): + nonlocal staged + # Reject paths outside the root + try: + os.path.relpath(abs_path, self.root_folder) + except ValueError: + print(f" [skip] {abs_path}: outside root folder") + return + + if not os.path.lexists(abs_path): + print(f" [skip] {abs_path}: path does not exist") + return + + cur.execute( + "INSERT OR IGNORE INTO staging (root_folder, absolute_path, added_at) " + "VALUES (?, ?, ?)", + (self.root_folder, abs_path, added_at) + ) + if cur.rowcount: + staged += 1 + + for raw in paths: + abs_path = os.path.join(self.root_folder, raw) + + if os.path.isdir(abs_path): + # Expand directory recursively + for dirpath, dirnames, filenames in os.walk(abs_path, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in skip_names] + for fname in filenames: + if fname in skip_names: + continue + stage_path(os.path.join(dirpath, fname)) + else: + stage_path(abs_path) + + conn.commit() + conn.close() + print(f" {staged} file(s) added to staging.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' to stage files.") + return + + print(f"Staged files ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel}") + + def cmd_remove(self, paths: list[str]): + """Remove file(s) from the staging area without touching the actual files.""" + root_folder = os.path.abspath(self.root_folder) + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + removed = 0 + + for raw in paths: + abs_path = os.path.abspath(raw) + cur.execute( + "DELETE FROM staging WHERE root_folder=? AND absolute_path=?", + (root_folder, abs_path) + ) + if cur.rowcount: + rel = os.path.relpath(abs_path, root_folder) + print(f" Unstaged: {rel}") + removed += 1 + else: + rel = os.path.relpath(abs_path, root_folder) + print(f" [not staged] {rel}") + + conn.commit() + conn.close() + print(f" {removed} file(s) removed from staging.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' to stage files.") + return + + print(f"Staged files ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel}") + + def cmd_commit(self, message: str = ""): + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + + # ── Load staged paths ──────────────────────────────────────────────────── + staged_rows = cur.execute( + "SELECT absolute_path FROM staging WHERE root_folder = ? ORDER BY absolute_path", + (self.root_folder,) + ).fetchall() + + if not staged_rows: + conn.close() + sys.exit("Nothing staged. Use 'add' to stage files before committing.") + + staged_paths = [r["absolute_path"] for r in staged_rows] + + # ── Previous snapshot for hard-link deduplication ──────────────────────── + prev_row = cur.execute( + "SELECT snapshot_path FROM versions " + "WHERE root_folder=? ORDER BY id DESC LIMIT 1", + (self.root_folder,) + ).fetchone() + prev_snapshot = prev_row["snapshot_path"] if prev_row else None + + committed_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + commit_hash = uuid.uuid4().hex + running_user = owner_name(os.getuid()) + snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) + + # ── Collect metadata for each staged file ─────────────────────────────── + print(f"Collecting metadata for {len(staged_paths)} staged file(s)…") + entries = [] + for abs_path in staged_paths: + e = collect_metadata(abs_path, self.root_folder) + if "error" in e: + print(f" [skip] {e['relative_path']}: {e['error']}") + else: + entries.append(e) + + if not entries: + conn.close() + sys.exit("No readable staged files — commit aborted.") + + total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") + file_count = sum(1 for e in entries if e.get("file_type") == "file") + + # ── rsync only the staged relative paths ──────────────────────────────── + rel_paths = [e["relative_path"] for e in entries] + print(f" {file_count} file(s), {total_bytes:,} bytes") + print(f" Creating rsync snapshot → {snapshot_path}") + + ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot, rel_paths) + if not ok: + conn.close() + sys.exit("rsync failed — commit aborted.") + + # ── Insert version row ─────────────────────────────────────────────────── + cur.execute( + """INSERT INTO versions + (root_folder, commit_hash, committed_at, owner_name, message, + snapshot_path, file_count, total_bytes) + VALUES (?,?,?,?,?,?,?,?)""", + (self.root_folder, commit_hash, committed_at, running_user, message, + snapshot_path, file_count, total_bytes) + ) + version_id = cur.lastrowid + + # ── Bulk-insert file entries ───────────────────────────────────────────── + cols = [ + "version_id", "root_folder", "relative_path", "absolute_path", + "file_name", "file_type", "md5_hash", + "lstat", + "permissions_octal", "permissions_str", "owner_name", "group_name", + "setuid", "setgid", "sticky", + "acl_text", "xattrs", "security_context", "symlink_target", + ] + placeholders = ",".join("?" * len(cols)) + col_str = ",".join(cols) + + cur.executemany( + f"INSERT INTO file_entries ({col_str}) VALUES ({placeholders})", + [ + tuple( + version_id if c == "version_id" else + self.root_folder if c == "root_folder" else + e.get(c) + for c in cols + ) + for e in entries + ] + ) + + # ── Clear staging after a successful commit ────────────────────────────── + cur.execute("DELETE FROM staging WHERE root_folder=?", (self.root_folder,)) + + conn.commit() + conn.close() + + print(f"\n Committed {commit_hash} at {committed_at}") + print(f" Short hash : {commit_hash[:12]}") + print(f" Owner : {running_user}") + if message: + print(f" Message : {message}") + + + def cmd_log(self): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT commit_hash, committed_at, owner_name, message, file_count, total_bytes " + "FROM versions WHERE root_folder=? ORDER BY id", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("No versions yet. Run 'commit' first.") + return + + print(f"{'COMMIT HASH':<34} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") + print("─" * 100) + for r in rows: + msg = (r["message"] or "")[:35] + print(f"{r['commit_hash']:<34} {r['owner_name']:<16} {r['committed_at']:<28}" + f"{r['file_count']:>7} {r['total_bytes']:>15,} {msg}") + + + def cmd_diff(self, c1: str, c2: str): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + + def get_files(chash): + vid = conn.execute( + "SELECT id FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, chash + "%") + ).fetchone() + if not vid: + conn.close() + sys.exit(f"Commit '{chash}' not found.") + rows = conn.execute( + "SELECT relative_path, file_type, md5_hash, permissions_octal, " + " owner_name, group_name, lstat " + "FROM file_entries WHERE version_id=?", (vid["id"],) + ).fetchall() + result = {} + for r in rows: + rec = dict(r) + # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. + rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} + result[r["relative_path"]] = rec + return result + + files1 = get_files(c1) + files2 = get_files(c2) + conn.close() + + all_paths = sorted(set(files1) | set(files2)) + added = deleted = modified = unchanged = 0 + + print(f"Diff {c1} → {c2} ({root_folder})\n") + print(f"{'STATUS':<10} {'PATH'}") + print("─" * 70) + + for p in all_paths: + # if p in files1 and p not in files2: + # print(f"{'DELETED':<10} {p:<40} in {c2}") + # deleted += 1 + # elif p in files2 and p not in files1: + # print(f"{'ADDED':<10} {p:<40} in {c2}") + # added += 1 + if p not in files1: + print(f"{'ADDED':<10} {p:<40} in {c2}") + added += 1 + elif p not in files2: + print(f"{'DELETED':<10} {p:<40} in {c2}") + deleted += 1 + else: + f1, f2 = files1[p], files2[p] + changes = [] + if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: + changes.append("content") + if f1["permissions_octal"] != f2["permissions_octal"]: + changes.append("perms") + if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: + changes.append("owner") + if f1["lstat"].get("st_size") != f2["lstat"].get("st_size"): + changes.append("size") + if changes: + print(f"{'MODIFIED':<10} {p} [{', '.join(changes)}]") + modified += 1 + else: + unchanged += 1 + + print(f"\nSummary: +{added} added -{deleted} deleted ~{modified} modified ={unchanged} unchanged") + + + def cmd_restore(self, commit_hash: str): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + row = conn.execute( + "SELECT commit_hash, snapshot_path FROM versions " + "WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, commit_hash + "%") + ).fetchone() + conn.close() + + if not row: + sys.exit(f"Commit '{commit_hash}' not found.") + + full_hash = row["commit_hash"] + snapshot = row["snapshot_path"] + if not os.path.isdir(snapshot): + sys.exit(f"Snapshot directory missing: {snapshot}") + + print(f"Restoring {commit_hash} from {snapshot} → {root_folder}") + cmd = [ + "rsync", "-aAXH", "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + snapshot.rstrip("/") + "/", + root_folder.rstrip("/") + "/", + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): + sys.exit(f"rsync restore failed:\n{result.stderr}") + print(f" Restored to {full_hash}") + + +# ─────────────────────────── CLI ───────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser( + description="dsi_vcs — rsync-based file version control with full Linux metadata" + ) + sub = parser.add_subparsers(dest="command", required=True) + + p_init = sub.add_parser("init", help="Initialize a new repo") + # p_init.add_argument("root_folder") + + p_add = sub.add_parser("add", help="add file or directory to staging") + p_add.add_argument('files', nargs='+', type=str) + + p_remove = sub.add_parser("remove", help="remove file or directory from staging") + p_remove.add_argument('files', nargs='+', type=str) + + p_commit = sub.add_parser("commit", help="Snapshot and commit staged files") + p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) + + p_log = sub.add_parser("log", help="List all commits") + + p_diff = sub.add_parser("diff", help="Diff two commits") + p_diff.add_argument("c1") + p_diff.add_argument("c2") + + p_restore = sub.add_parser("restore", help="Restore a version") + p_restore.add_argument("root_folder") + p_restore.add_argument("version") + + args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) + + # parser.print_help() + # parser.format_help() + if args.command == "init": + vcs = DSIVCS(os.getcwd()) + elif args.command == "add": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_add(args.files) + elif args.command == "remove": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_remove(args.files) + elif args.command == "commit": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_commit(args.message) + elif args.command == "diff": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_diff(args.c1, args.c2) + elif args.command == "log": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_log() + # elif args.command == "diff": cmd_diff(args.root_folder, args.v1, args.v2) + # elif args.command == "restore": cmd_restore(args.root_folder, args.version) + +if __name__ == "__main__": + # print("\n=== dsi-vcs: rsync-based file version control ===\n") + # print(os.getcwd()) + main() \ No newline at end of file diff --git a/tools/version_control/requirements.txt b/tools/version_control/requirements.txt new file mode 100644 index 00000000..11258685 --- /dev/null +++ b/tools/version_control/requirements.txt @@ -0,0 +1 @@ +pyxattr>=0.8.1 \ No newline at end of file diff --git a/tools/version_control/vcs_db.py b/tools/version_control/vcs_db.py new file mode 100644 index 00000000..72ef6990 --- /dev/null +++ b/tools/version_control/vcs_db.py @@ -0,0 +1,94 @@ +import sqlite3 +import os + +# ─────────────────────────── CONFIG ────────────────────────────────────────── + +DB_NAME = ".dsi_vcs.db" # SQLite DB stored inside the root folder +SNAPSHOTS_DIR = ".dsi_vcs_snapshots" # rsync snapshot copies live here + +# ─────────────────────────── DATABASE ──────────────────────────────────────── + +SCHEMA = """ +CREATE TABLE IF NOT EXISTS versions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + root_folder TEXT NOT NULL, + commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex + committed_at TEXT NOT NULL, -- ISO-8601 timestamp + owner_name TEXT NOT NULL, -- Linux user who ran the commit + message TEXT, + snapshot_path TEXT NOT NULL, -- path to rsync copy + file_count INTEGER NOT NULL, + total_bytes INTEGER NOT NULL, + UNIQUE(root_folder, commit_hash) +); + +CREATE TABLE IF NOT EXISTS file_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version_id INTEGER NOT NULL REFERENCES versions(id) ON DELETE CASCADE, + root_folder TEXT NOT NULL, -- partition key / lookup key + relative_path TEXT NOT NULL, -- path relative to root + absolute_path TEXT NOT NULL, + + -- ── Identity ────────────────────────────────────────────────────────── + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- file/dir/symlink/block/char/fifo/socket + + -- ── Content hash ────────────────────────────────────────────────────── + md5_hash TEXT, -- NULL for non-regular files + + -- ── lstat(2) — all raw os.lstat() fields packed as a JSON object ────── + -- Keys: st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, + -- st_atime, st_mtime, st_ctime, st_blocks, st_blksize + lstat TEXT NOT NULL, + + -- ── Human-readable permission strings ───────────────────────────────── + permissions_octal TEXT, -- e.g. "0o755" + permissions_str TEXT, -- e.g. "rwxr-xr-x" + owner_name TEXT, + group_name TEXT, + + -- ── Special bits ────────────────────────────────────────────────────── + setuid INTEGER DEFAULT 0, + setgid INTEGER DEFAULT 0, + sticky INTEGER DEFAULT 0, + + -- ── ACL (POSIX) ─────────────────────────────────────────────────────── + acl_text TEXT, -- raw getfacl output + + -- ── Extended attributes ─────────────────────────────────────────────── + xattrs TEXT, -- JSON dict of xattr key→value + + -- ── Symlink target ─────────────────────────────────────────────────── + symlink_target TEXT, + + -- ── SELinux / AppArmor context ──────────────────────────────────────── + security_context TEXT +); + +CREATE INDEX IF NOT EXISTS idx_file_entries_root + ON file_entries(root_folder, version_id); + +CREATE INDEX IF NOT EXISTS idx_file_entries_path + ON file_entries(root_folder, relative_path); + +CREATE TABLE IF NOT EXISTS staging ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + root_folder TEXT NOT NULL, + absolute_path TEXT NOT NULL, + added_at TEXT NOT NULL, -- ISO-8601 timestamp + UNIQUE(root_folder, absolute_path) +); +""" + + +def open_db(root_folder: str) -> sqlite3.Connection: + snaps = os.path.join(root_folder, SNAPSHOTS_DIR) + os.makedirs(snaps, exist_ok=True) + db_path = os.path.join(snaps, DB_NAME) + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA foreign_keys = ON") + conn.execute("PRAGMA journal_mode = WAL") + conn.executescript(SCHEMA) + conn.commit() + return conn diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py new file mode 100644 index 00000000..a70aeda8 --- /dev/null +++ b/tools/version_control/vcs_metadata_helper.py @@ -0,0 +1,248 @@ +import os +import stat +import hashlib +import sqlite3 +import json +import subprocess +import pwd +import grp +import sys +from typing import Optional + +# ─────────────────────────── METADATA HELPERS ──────────────────────────────── + +def file_type_str(mode: int) -> str: + if stat.S_ISREG(mode): return "file" + if stat.S_ISDIR(mode): return "dir" + if stat.S_ISLNK(mode): return "symlink" + if stat.S_ISBLK(mode): return "block" + if stat.S_ISCHR(mode): return "char" + if stat.S_ISFIFO(mode): return "fifo" + if stat.S_ISSOCK(mode): return "socket" + return "unknown" + + +def permission_str(mode: int) -> str: + """Convert mode bits → rwxrwxrwx string (no file-type prefix).""" + chars = [] + for who in (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, + stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, + stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH): + chars.append("+" if mode & who else "-") + # Replace x with s/t for setuid/setgid/sticky + if mode & stat.S_ISUID: + chars[2] = "s" if chars[2] == "+" else "S" + if mode & stat.S_ISGID: + chars[5] = "s" if chars[5] == "+" else "S" + if mode & stat.S_ISVTX: + chars[8] = "t" if chars[8] == "+" else "T" + return "".join(chars) + + +def owner_name(uid: int) -> str: + try: return pwd.getpwuid(uid).pw_name + except KeyError: return str(uid) + + +def group_name(gid: int) -> str: + try: return grp.getgrgid(gid).gr_name + except KeyError: return str(gid) + + +def get_acl(path: str) -> Optional[str]: + """Return raw getfacl output, or None if unavailable.""" + try: + if sys.platform == "darwin": + result = subprocess.run( + # ["getfacl", "--omit-header", "--absolute-names", path], + ["ls", "-le", path], + capture_output=True, text=True, timeout=5 + ) + lines = result.stdout.splitlines() + + acl_entries = [] + + for line in lines: + line = line.strip() + if line.startswith(tuple(str(i) + ":" for i in range(10))): + # Example line: "0: user:azad allow read,write" + parts = line.split("user:") + if len(parts) > 1: + acl_part = parts[1].strip() + acl_entries.append(acl_part) + + return ";".join(acl_entries) if acl_entries else None + else: + result = subprocess.run( + ["getfacl", "--omit-header", "--absolute-names", path], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() or None + except (FileNotFoundError, subprocess.TimeoutExpired): + return None + + +def set_acl(path: str, acl_string: str) -> Optional[str]: + """Return raw getfacl output, or None if unavailable.""" + acl_list = acl_string.split(";") + for acl_entry in acl_list: + try: + acls = acl_entry.split() + acl_perm = "user:" + acls[0] + " allow " + acls[2] + print(acl_perm) + result = subprocess.run( + ["chmod", "+a", acl_perm, path], + capture_output=True, text=True, timeout=5 + ) + if result.returncode != 0: + print(f"Failed to set ACL entry '{acl_entry}' on {path}: {result.stderr}") + except Exception as e: + print(f"Error setting ACL entry '{acl_entry}' on {path}: {e}") + + +def get_xattrs(path: str) -> Optional[str]: + """Return JSON dict of extended attributes, or None.""" + try: + import xattr # pyxattr + attrs = {} + for key in xattr.listxattr(path, symlink=True): + try: + val = xattr.getxattr(path, key, symlink=True) + attrs[key] = val.decode("utf-8", errors="replace") + except OSError: + attrs[key] = "" + return json.dumps(attrs) if attrs else None + except ImportError: + # Fallback: use getfattr + try: + result = subprocess.run( + ["getfattr", "-d", "-m", "-", "--absolute-names", path], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() or None + except FileNotFoundError: + return None + except OSError: + return None + + +def get_security_context(path: str) -> Optional[str]: + """Return SELinux/AppArmor security context if available.""" + try: + result = subprocess.run( + ["ls", "-Z", path], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + parts = result.stdout.strip().split() + # ls -Z format: context file (or just file if no context) + if len(parts) >= 2 and ":" in parts[0]: + return parts[0] + return None + except Exception: + return None + + +def md5_hash(path: str, chunk_size: int = 1 << 20) -> Optional[str]: + """Stream-hash a file in 1 MB chunks. Returns None on error.""" + h = hashlib.md5() + try: + with open(path, "rb") as f: + while chunk := f.read(chunk_size): + h.update(chunk) + return h.hexdigest() + except (PermissionError, OSError): + return None + + +def collect_metadata(abs_path: str, root_folder: str) -> dict: + """ + Gather all available metadata for a single path. + + Returns a dict with two logical groups: + • lstat — raw os.lstat() result stored as a nested dict (→ JSON column) + • all other keys — each stored in its own column + """ + rel_path = os.path.relpath(abs_path, root_folder) + + try: + s = os.lstat(abs_path) + except OSError as e: + return {"relative_path": rel_path, "error": str(e)} + + mode = s.st_mode + ftype = file_type_str(mode) + + # ── lstat dict (raw kernel values only) ────────────────────────────────── + lstat_dict = { + "st_mode": mode, + "st_ino": s.st_ino, + "st_dev": s.st_dev, + "st_nlink": s.st_nlink, + "st_uid": s.st_uid, + "st_gid": s.st_gid, + "st_size": s.st_size, + "st_atime": s.st_atime, + "st_mtime": s.st_mtime, + "st_ctime": s.st_ctime, + "st_blocks": getattr(s, "st_blocks", None), + "st_blksize": getattr(s, "st_blksize", None), + } + + # ── all other (derived / external) attributes — own columns ────────────── + entry = { + "relative_path": rel_path, + "absolute_path": abs_path, + "file_name": os.path.basename(abs_path), + "file_type": ftype, + + # serialised lstat dict → stored as TEXT (JSON) in a single column + "lstat": json.dumps(lstat_dict), + + # human-readable permission strings + "permissions_octal": oct(stat.S_IMODE(mode)), + "permissions_str": permission_str(mode), + "owner_name": owner_name(s.st_uid), + "group_name": group_name(s.st_gid), + + # special-bit flags + "setuid": int(bool(mode & stat.S_ISUID)), + "setgid": int(bool(mode & stat.S_ISGID)), + "sticky": int(bool(mode & stat.S_ISVTX)), + + # external / subprocess-derived attributes + "acl_text": get_acl(abs_path), + "xattrs": get_xattrs(abs_path), + "security_context": get_security_context(abs_path), + "symlink_target": os.readlink(abs_path) if ftype == "symlink" else None, + + # content hash — regular files only + "md5_hash": md5_hash(abs_path) if ftype == "file" else None, + + # convenience shortcut used by walk_folder aggregation (not stored) + "_st_size": s.st_size, + } + print(f"ACL text for {rel_path}: {entry['acl_text']}") + return entry + + +# def walk_folder(root_folder: str) -> list[dict]: +# """Recursively collect metadata for every entry under root_folder.""" +# entries = [] +# skip_dirs = {DB_NAME, SNAPSHOTS_DIR} + +# for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): +# # Prune internal bookkeeping dirs +# dirnames[:] = [d for d in dirnames if d not in skip_dirs] + +# # Include the directory entry itself (except root) +# if dirpath != root_folder: +# entries.append(collect_metadata(dirpath, root_folder)) + +# for fname in filenames: +# if fname == DB_NAME: +# continue +# abs_path = os.path.join(dirpath, fname) +# entries.append(collect_metadata(abs_path, root_folder)) + +# return entries From 9f5c3cbe376b7f9dee952b4c0df32345754c9178 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 26 Mar 2026 19:25:27 -0600 Subject: [PATCH 020/109] Fixed acl in versioning --- tools/version_control/vcs_metadata_helper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index a70aeda8..4eae1bcd 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -87,6 +87,8 @@ def set_acl(path: str, acl_string: str) -> Optional[str]: acl_list = acl_string.split(";") for acl_entry in acl_list: try: + if len(acl_entry) == 0: + continue acls = acl_entry.split() acl_perm = "user:" + acls[0] + " allow " + acls[2] print(acl_perm) From 21b915e428b9fdfaac7f3e8331b56f2a7b5e9321 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 08:54:51 -0600 Subject: [PATCH 021/109] VCS: Fix permission str and cli usage. --- tools/version_control/dsi_vcs.py | 22 +++++++++++--------- tools/version_control/vcs_metadata_helper.py | 17 ++++++++------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index 44ebaa56..80a1e551 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -5,11 +5,13 @@ and stores versioned snapshots in SQLite. Usage: - python dsi_vcs.py init # init repo - python dsi_vcs.py commit [message] # commit a new version - python dsi_vcs.py log # list versions - python dsi_vcs.py diff # diff two versions - python dsi_vcs.py restore # restore a version + python dsi_vcs.py init # init repo in current directory + python dsi_vcs.py add ... # stage paths for the next commit + python dsi_vcs.py remove ... # unstage paths + python dsi_vcs.py commit [message] # commit a new version + python dsi_vcs.py log # list versions + python dsi_vcs.py diff # diff two versions + python dsi_vcs.py restore # restore a version Requirements: pip install pyxattr # for extended attributes @@ -176,7 +178,7 @@ def stage_path(abs_path: str): for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") - + def cmd_remove(self, paths: list[str]): """Remove file(s) from the staging area without touching the actual files.""" root_folder = os.path.abspath(self.root_folder) @@ -483,7 +485,6 @@ def main(): p_diff.add_argument("c2") p_restore = sub.add_parser("restore", help="Restore a version") - p_restore.add_argument("root_folder") p_restore.add_argument("version") args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) @@ -507,10 +508,11 @@ def main(): elif args.command == "log": vcs = DSIVCS(os.getcwd()) vcs.cmd_log() - # elif args.command == "diff": cmd_diff(args.root_folder, args.v1, args.v2) - # elif args.command == "restore": cmd_restore(args.root_folder, args.version) + elif args.command == "restore": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_restore(args.version) if __name__ == "__main__": # print("\n=== dsi-vcs: rsync-based file version control ===\n") # print(os.getcwd()) - main() \ No newline at end of file + main() diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 4eae1bcd..6daea75b 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -25,17 +25,19 @@ def file_type_str(mode: int) -> str: def permission_str(mode: int) -> str: """Convert mode bits → rwxrwxrwx string (no file-type prefix).""" chars = [] - for who in (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, - stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, - stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH): - chars.append("+" if mode & who else "-") + for bit, char in ( + (stat.S_IRUSR, "r"), (stat.S_IWUSR, "w"), (stat.S_IXUSR, "x"), + (stat.S_IRGRP, "r"), (stat.S_IWGRP, "w"), (stat.S_IXGRP, "x"), + (stat.S_IROTH, "r"), (stat.S_IWOTH, "w"), (stat.S_IXOTH, "x"), + ): + chars.append(char if mode & bit else "-") # Replace x with s/t for setuid/setgid/sticky if mode & stat.S_ISUID: - chars[2] = "s" if chars[2] == "+" else "S" + chars[2] = "s" if chars[2] == "x" else "S" if mode & stat.S_ISGID: - chars[5] = "s" if chars[5] == "+" else "S" + chars[5] = "s" if chars[5] == "x" else "S" if mode & stat.S_ISVTX: - chars[8] = "t" if chars[8] == "+" else "T" + chars[8] = "t" if chars[8] == "x" else "T" return "".join(chars) @@ -224,7 +226,6 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: # convenience shortcut used by walk_folder aggregation (not stored) "_st_size": s.st_size, } - print(f"ACL text for {rel_path}: {entry['acl_text']}") return entry From 51d8013837511b527a1e12fb61e5e25787f713ad Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 09:14:55 -0600 Subject: [PATCH 022/109] VCS: create complete snapshots. The rel_path branch results in only including staged files in the snapshot. Complete snapshots of the remote folder are needed so unchanged files are not omitted in a snapshot. --- tools/version_control/dsi_vcs.py | 71 ++++++++------------ tools/version_control/vcs_metadata_helper.py | 19 ++++++ 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index 80a1e551..ef185f50 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -38,15 +38,11 @@ def rsync_snapshot( root_folder: str, dest_path: str, prev_snapshot: Optional[str] = None, - rel_paths: Optional[list[str]] = None, ) -> bool: """ - Copy files from root_folder → dest_path using rsync hard-link deduplication. - If rel_paths is given, only those paths are synced (via --files-from). + Copy the full root_folder tree to dest_path using rsync hard-link deduplication. If prev_snapshot is provided, unchanged files are hard-linked (saves disk). """ - import tempfile - os.makedirs(dest_path, exist_ok=True) cmd = ["rsync", "-aAXH"] # for linux if sys.platform == "darwin": @@ -55,38 +51,22 @@ def rsync_snapshot( if prev_snapshot and os.path.isdir(prev_snapshot): cmd += ["--link-dest", os.path.abspath(prev_snapshot)] - # Write a temp file-list when only staging specific paths - files_from_tmp = None - if rel_paths is not None: - tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) - tmp.write("\n".join(rel_paths)) - tmp.close() - files_from_tmp = tmp.name - cmd += ["--files-from", files_from_tmp] - else: - cmd += [ - "--delete", - "--exclude", DB_NAME, - "--exclude", SNAPSHOTS_DIR, - ] + cmd += [ + "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + ] cmd += [ root_folder.rstrip("/") + "/", dest_path.rstrip("/") + "/", ] - try: - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode not in (0, 24): # 24 = some files vanished (ok) - print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) - return False - if sys.platform == "darwin": - for rel_path in rel_paths: - set_acl(dest_path, get_acl(rel_path) or "") - return True - finally: - if files_from_tmp: - os.unlink(files_from_tmp) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): # 24 = some files vanished (ok) + print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) + return False + return True # ─────────────────────────── COMMANDS ──────────────────────────────────────── @@ -259,33 +239,38 @@ def cmd_commit(self, message: str = ""): running_user = owner_name(os.getuid()) snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) - # ── Collect metadata for each staged file ─────────────────────────────── - print(f"Collecting metadata for {len(staged_paths)} staged file(s)…") - entries = [] + # ── Validate staged files before creating the snapshot ───────────────── + print(f"Validating {len(staged_paths)} staged file(s)…") + valid_staged = 0 for abs_path in staged_paths: e = collect_metadata(abs_path, self.root_folder) if "error" in e: print(f" [skip] {e['relative_path']}: {e['error']}") else: - entries.append(e) + valid_staged += 1 - if not entries: + if valid_staged == 0: conn.close() sys.exit("No readable staged files — commit aborted.") - total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") - file_count = sum(1 for e in entries if e.get("file_type") == "file") - - # ── rsync only the staged relative paths ──────────────────────────────── - rel_paths = [e["relative_path"] for e in entries] - print(f" {file_count} file(s), {total_bytes:,} bytes") + # ── Create a complete snapshot of the current repository tree ─────────── print(f" Creating rsync snapshot → {snapshot_path}") - ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot, rel_paths) + ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot) if not ok: conn.close() sys.exit("rsync failed — commit aborted.") + # ── Collect metadata for the complete committed tree ─────────────────── + entries = collect_root_file_metadata(self.root_folder) + if not entries: + conn.close() + sys.exit("No readable files — commit aborted.") + + total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") + file_count = sum(1 for e in entries if e.get("file_type") == "file") + print(f" {file_count} file(s), {total_bytes:,} bytes") + # ── Insert version row ─────────────────────────────────────────────────── cur.execute( """INSERT INTO versions diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 6daea75b..122b765c 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,6 +229,25 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry +def collect_root_file_metadata(root_folder: str) -> list[dict]: + """Collect metadata for every tracked file under root_folder.""" + entries = [] + skip_names = {DB_NAME, SNAPSHOTS_DIR} + + for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in skip_names] + for fname in filenames: + if fname in skip_names: + continue + entry = collect_metadata(os.path.join(dirpath, fname), root_folder) + if "error" in entry: + print(f" [skip] {entry['relative_path']}: {entry['error']}") + else: + entries.append(entry) + + return entries + + # def walk_folder(root_folder: str) -> list[dict]: # """Recursively collect metadata for every entry under root_folder.""" # entries = [] From 62cc1de5e682ce01e2f1b330e421d72027bcdab3 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 09:26:24 -0600 Subject: [PATCH 023/109] VCS: track directory metadata. Previous metadata tracking was only for files. This tracks paths (both files and directories) so we can preserve permissions for directory trees. --- tools/version_control/dsi_vcs.py | 23 ++++++++++---------- tools/version_control/vcs_metadata_helper.py | 12 ++++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index ef185f50..a5476aa0 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -130,6 +130,7 @@ def stage_path(abs_path: str): # Expand directory recursively for dirpath, dirnames, filenames in os.walk(abs_path, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] + stage_path(dirpath) for fname in filenames: if fname in skip_names: continue @@ -139,7 +140,7 @@ def stage_path(abs_path: str): conn.commit() conn.close() - print(f" {staged} file(s) added to staging.") + print(f" {staged} path(s) added to staging.") """Show files currently in the staging area.""" root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) @@ -151,16 +152,16 @@ def stage_path(abs_path: str): conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage files.") + print("Nothing staged. Use 'add ...' to stage paths.") return - print(f"Staged files ({len(rows)}):") + print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") def cmd_remove(self, paths: list[str]): - """Remove file(s) from the staging area without touching the actual files.""" + """Remove path(s) from the staging area without touching the actual files.""" root_folder = os.path.abspath(self.root_folder) db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) if not os.path.isfile(db_path): @@ -186,7 +187,7 @@ def cmd_remove(self, paths: list[str]): conn.commit() conn.close() - print(f" {removed} file(s) removed from staging.") + print(f" {removed} path(s) removed from staging.") """Show files currently in the staging area.""" root_folder = os.path.abspath(root_folder) conn = open_db(root_folder) @@ -198,10 +199,10 @@ def cmd_remove(self, paths: list[str]): conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage files.") + print("Nothing staged. Use 'add ...' to stage paths.") return - print(f"Staged files ({len(rows)}):") + print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") @@ -239,8 +240,8 @@ def cmd_commit(self, message: str = ""): running_user = owner_name(os.getuid()) snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) - # ── Validate staged files before creating the snapshot ───────────────── - print(f"Validating {len(staged_paths)} staged file(s)…") + # ── Validate staged paths before creating the snapshot ───────────────── + print(f"Validating {len(staged_paths)} staged path(s)…") valid_staged = 0 for abs_path in staged_paths: e = collect_metadata(abs_path, self.root_folder) @@ -251,7 +252,7 @@ def cmd_commit(self, message: str = ""): if valid_staged == 0: conn.close() - sys.exit("No readable staged files — commit aborted.") + sys.exit("No readable staged paths — commit aborted.") # ── Create a complete snapshot of the current repository tree ─────────── print(f" Creating rsync snapshot → {snapshot_path}") @@ -262,7 +263,7 @@ def cmd_commit(self, message: str = ""): sys.exit("rsync failed — commit aborted.") # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_root_file_metadata(self.root_folder) + entries = collect_root_metadata(self.root_folder) if not entries: conn.close() sys.exit("No readable files — commit aborted.") diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 122b765c..819bf100 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,13 +229,21 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry -def collect_root_file_metadata(root_folder: str) -> list[dict]: - """Collect metadata for every tracked file under root_folder.""" + +def collect_root_metadata(root_folder: str) -> list[dict]: + """Collect metadata for every tracked directory and file under root_folder.""" entries = [] skip_names = {DB_NAME, SNAPSHOTS_DIR} for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] + if dirpath != root_folder: + entry = collect_metadata(dirpath, root_folder) + if "error" in entry: + print(f" [skip] {entry['relative_path']}: {entry['error']}") + else: + entries.append(entry) + for fname in filenames: if fname in skip_names: continue From e956f113a176fb9a95ffd7dfbc1ef7d1d7692f92 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 10:19:56 -0600 Subject: [PATCH 024/109] VCS: add delete command for staging path deletion. --- tools/version_control/dsi_vcs.py | 127 +++++++++++++++---- tools/version_control/vcs_db.py | 1 + tools/version_control/vcs_metadata_helper.py | 16 ++- 3 files changed, 109 insertions(+), 35 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index a5476aa0..53901553 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -7,6 +7,7 @@ Usage: python dsi_vcs.py init # init repo in current directory python dsi_vcs.py add ... # stage paths for the next commit + python dsi_vcs.py delete ... # stage paths for deletion python dsi_vcs.py remove ... # unstage paths python dsi_vcs.py commit [message] # commit a new version python dsi_vcs.py log # list versions @@ -26,6 +27,7 @@ import json import datetime import argparse +import shutil from pathlib import Path from typing import Optional @@ -69,6 +71,26 @@ def rsync_snapshot( return True +def snapshot_target(snapshot_path: str, relative_path: str) -> str: + target = os.path.abspath(os.path.join(snapshot_path, relative_path)) + snapshot_root = os.path.abspath(snapshot_path) + if os.path.commonpath([snapshot_root, target]) != snapshot_root: + raise ValueError(f"Snapshot path escapes snapshot root: {relative_path}") + return target + + +def apply_snapshot_deletes(snapshot_path: str, root_folder: str, staged_deletes: list[str]) -> None: + for abs_path in staged_deletes: + rel_path = os.path.relpath(abs_path, root_folder) + if rel_path in ("", "."): + raise ValueError("Refusing to stage repository root for deletion.") + target = snapshot_target(snapshot_path, rel_path) + if os.path.isdir(target) and not os.path.islink(target): + shutil.rmtree(target) + elif os.path.lexists(target): + os.unlink(target) + + # ─────────────────────────── COMMANDS ──────────────────────────────────────── class DSIVCS(): @@ -104,27 +126,22 @@ def cmd_add(self, paths: list[str]): def stage_path(abs_path: str): nonlocal staged - # Reject paths outside the root - try: - os.path.relpath(abs_path, self.root_folder) - except ValueError: - print(f" [skip] {abs_path}: outside root folder") - return + abs_path = os.path.abspath(abs_path) if not os.path.lexists(abs_path): print(f" [skip] {abs_path}: path does not exist") return cur.execute( - "INSERT OR IGNORE INTO staging (root_folder, absolute_path, added_at) " - "VALUES (?, ?, ?)", - (self.root_folder, abs_path, added_at) + "INSERT OR REPLACE INTO staging (root_folder, absolute_path, action, added_at) " + "VALUES (?, ?, ?, ?)", + (self.root_folder, abs_path, "add", added_at) ) if cur.rowcount: staged += 1 for raw in paths: - abs_path = os.path.join(self.root_folder, raw) + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(self.root_folder, raw)) if os.path.isdir(abs_path): # Expand directory recursively @@ -145,20 +162,63 @@ def stage_path(abs_path: str): root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) rows = conn.execute( - "SELECT absolute_path, added_at FROM staging " + "SELECT absolute_path, action, added_at FROM staging " "WHERE root_folder=? ORDER BY absolute_path", (root_folder,) ).fetchall() conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage paths.") + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") return print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) - print(f" {rel}") + print(f" {rel} [{r['action']}]") + + def cmd_delete(self, paths: list[str]): + """Stage path(s) for deletion in the next commit.""" + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + added_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + staged = 0 + + for raw in paths: + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(self.root_folder, raw)) + cur.execute( + "INSERT OR REPLACE INTO staging (root_folder, absolute_path, action, added_at) " + "VALUES (?, ?, ?, ?)", + (self.root_folder, abs_path, "delete", added_at) + ) + if cur.rowcount: + staged += 1 + + conn.commit() + conn.close() + print(f" {staged} path(s) staged for deletion.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, action, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") + return + + print(f"Staged paths ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel} [{r['action']}]") def cmd_remove(self, paths: list[str]): """Remove path(s) from the staging area without touching the actual files.""" @@ -172,7 +232,7 @@ def cmd_remove(self, paths: list[str]): removed = 0 for raw in paths: - abs_path = os.path.abspath(raw) + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(root_folder, raw)) cur.execute( "DELETE FROM staging WHERE root_folder=? AND absolute_path=?", (root_folder, abs_path) @@ -192,20 +252,20 @@ def cmd_remove(self, paths: list[str]): root_folder = os.path.abspath(root_folder) conn = open_db(root_folder) rows = conn.execute( - "SELECT absolute_path, added_at FROM staging " + "SELECT absolute_path, action, added_at FROM staging " "WHERE root_folder=? ORDER BY absolute_path", (root_folder,) ).fetchall() conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage paths.") + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") return print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) - print(f" {rel}") + print(f" {rel} [{r['action']}]") def cmd_commit(self, message: str = ""): db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) @@ -217,15 +277,16 @@ def cmd_commit(self, message: str = ""): # ── Load staged paths ──────────────────────────────────────────────────── staged_rows = cur.execute( - "SELECT absolute_path FROM staging WHERE root_folder = ? ORDER BY absolute_path", + "SELECT absolute_path, action FROM staging WHERE root_folder = ? ORDER BY absolute_path", (self.root_folder,) ).fetchall() if not staged_rows: conn.close() - sys.exit("Nothing staged. Use 'add' to stage files before committing.") + sys.exit("Nothing staged. Use 'add' or 'delete' before committing.") - staged_paths = [r["absolute_path"] for r in staged_rows] + staged_adds = [r["absolute_path"] for r in staged_rows if r["action"] == "add"] + staged_deletes = [r["absolute_path"] for r in staged_rows if r["action"] == "delete"] # ── Previous snapshot for hard-link deduplication ──────────────────────── prev_row = cur.execute( @@ -241,9 +302,9 @@ def cmd_commit(self, message: str = ""): snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) # ── Validate staged paths before creating the snapshot ───────────────── - print(f"Validating {len(staged_paths)} staged path(s)…") - valid_staged = 0 - for abs_path in staged_paths: + print(f"Validating {len(staged_rows)} staged path(s)…") + valid_staged = len(staged_deletes) + for abs_path in staged_adds: e = collect_metadata(abs_path, self.root_folder) if "error" in e: print(f" [skip] {e['relative_path']}: {e['error']}") @@ -262,11 +323,15 @@ def cmd_commit(self, message: str = ""): conn.close() sys.exit("rsync failed — commit aborted.") - # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_root_metadata(self.root_folder) - if not entries: + try: + apply_snapshot_deletes(snapshot_path, self.root_folder, staged_deletes) + except ValueError as e: conn.close() - sys.exit("No readable files — commit aborted.") + shutil.rmtree(snapshot_path, ignore_errors=True) + sys.exit(str(e)) + + # ── Collect metadata for the complete committed tree ─────────────────── + entries = collect_tree_metadata(snapshot_path, self.root_folder) total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") file_count = sum(1 for e in entries if e.get("file_type") == "file") @@ -458,10 +523,13 @@ def main(): p_add = sub.add_parser("add", help="add file or directory to staging") p_add.add_argument('files', nargs='+', type=str) + p_delete = sub.add_parser("delete", help="stage file or directory deletion") + p_delete.add_argument('files', nargs='+', type=str) + p_remove = sub.add_parser("remove", help="remove file or directory from staging") p_remove.add_argument('files', nargs='+', type=str) - p_commit = sub.add_parser("commit", help="Snapshot and commit staged files") + p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) p_log = sub.add_parser("log", help="List all commits") @@ -482,6 +550,9 @@ def main(): elif args.command == "add": vcs = DSIVCS(os.getcwd()) vcs.cmd_add(args.files) + elif args.command == "delete": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_delete(args.files) elif args.command == "remove": vcs = DSIVCS(os.getcwd()) vcs.cmd_remove(args.files) diff --git a/tools/version_control/vcs_db.py b/tools/version_control/vcs_db.py index 72ef6990..aaf21fca 100644 --- a/tools/version_control/vcs_db.py +++ b/tools/version_control/vcs_db.py @@ -75,6 +75,7 @@ id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, absolute_path TEXT NOT NULL, + action TEXT NOT NULL DEFAULT 'add', added_at TEXT NOT NULL, -- ISO-8601 timestamp UNIQUE(root_folder, absolute_path) ); diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 819bf100..a8734f7f 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,33 +229,35 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry - -def collect_root_metadata(root_folder: str) -> list[dict]: - """Collect metadata for every tracked directory and file under root_folder.""" +def collect_tree_metadata(scan_root: str, identity_root: str) -> list[dict]: + """Collect metadata under scan_root while storing paths under identity_root.""" entries = [] skip_names = {DB_NAME, SNAPSHOTS_DIR} - for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + for dirpath, dirnames, filenames in os.walk(scan_root, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] - if dirpath != root_folder: - entry = collect_metadata(dirpath, root_folder) + if dirpath != scan_root: + entry = collect_metadata(dirpath, scan_root) if "error" in entry: print(f" [skip] {entry['relative_path']}: {entry['error']}") else: + entry["absolute_path"] = os.path.join(identity_root, entry["relative_path"]) entries.append(entry) for fname in filenames: if fname in skip_names: continue - entry = collect_metadata(os.path.join(dirpath, fname), root_folder) + entry = collect_metadata(os.path.join(dirpath, fname), scan_root) if "error" in entry: print(f" [skip] {entry['relative_path']}: {entry['error']}") else: + entry["absolute_path"] = os.path.join(identity_root, entry["relative_path"]) entries.append(entry) return entries + # def walk_folder(root_folder: str) -> list[dict]: # """Recursively collect metadata for every entry under root_folder.""" # entries = [] From b14e5010649cc40cedf5d6f340da09c21d12ac49 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 13:44:55 -0600 Subject: [PATCH 025/109] VCS: move dsi_vcs utility under dsi, begin tests. This moves the dsi_vcs into dsi as a package under utils so it will be installed and available. This also begins tests for the utility. --- dsi/utils/version_control/__init__.py | 3 + dsi/utils/version_control/__main__.py | 5 ++ dsi/utils/version_control/cli.py | 68 ++++++++++++++++++ .../utils}/version_control/dsi_vcs.py | 71 +------------------ .../utils}/version_control/requirements.txt | 0 dsi/utils/version_control/tests/test_vcs.py | 70 ++++++++++++++++++ .../utils}/version_control/vcs_db.py | 0 .../version_control/vcs_metadata_helper.py | 2 + pyproject.toml | 3 +- 9 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 dsi/utils/version_control/__init__.py create mode 100644 dsi/utils/version_control/__main__.py create mode 100644 dsi/utils/version_control/cli.py rename {tools => dsi/utils}/version_control/dsi_vcs.py (88%) rename {tools => dsi/utils}/version_control/requirements.txt (100%) create mode 100644 dsi/utils/version_control/tests/test_vcs.py rename {tools => dsi/utils}/version_control/vcs_db.py (100%) rename {tools => dsi/utils}/version_control/vcs_metadata_helper.py (99%) diff --git a/dsi/utils/version_control/__init__.py b/dsi/utils/version_control/__init__.py new file mode 100644 index 00000000..64b89704 --- /dev/null +++ b/dsi/utils/version_control/__init__.py @@ -0,0 +1,3 @@ +from .dsi_vcs import DSIVCS + +__all__ = ["DSIVCS"] diff --git a/dsi/utils/version_control/__main__.py b/dsi/utils/version_control/__main__.py new file mode 100644 index 00000000..2efe6923 --- /dev/null +++ b/dsi/utils/version_control/__main__.py @@ -0,0 +1,5 @@ +from .cli import main + +if __name__ == "__main__": + main() + diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py new file mode 100644 index 00000000..97823901 --- /dev/null +++ b/dsi/utils/version_control/cli.py @@ -0,0 +1,68 @@ +import argparse +import datetime +import sys + +from .dsi_vcs import DSIVCS + +def main(): + parser = argparse.ArgumentParser( + description="dsi-vcs — rsync-based file version control with full Linux metadata" + ) + sub = parser.add_subparsers(dest="command", required=True) + + p_init = sub.add_parser("init", help="Initialize a new repo") + # p_init.add_argument("root_folder") + + p_add = sub.add_parser("add", help="add file or directory to staging") + p_add.add_argument('files', nargs='+', type=str) + + p_delete = sub.add_parser("delete", help="stage file or directory deletion") + p_delete.add_argument('files', nargs='+', type=str) + + p_remove = sub.add_parser("remove", help="remove file or directory from staging") + p_remove.add_argument('files', nargs='+', type=str) + + p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") + p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) + + p_log = sub.add_parser("log", help="List all commits") + + p_diff = sub.add_parser("diff", help="Diff two commits") + p_diff.add_argument("c1") + p_diff.add_argument("c2") + + p_restore = sub.add_parser("restore", help="Restore a version") + p_restore.add_argument("version") + + args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) + + # parser.print_help() + # parser.format_help() + if args.command == "init": + vcs = DSIVCS(os.getcwd()) + elif args.command == "add": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_add(args.files) + elif args.command == "delete": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_delete(args.files) + elif args.command == "remove": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_remove(args.files) + elif args.command == "commit": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_commit(args.message) + elif args.command == "diff": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_diff(args.c1, args.c2) + elif args.command == "log": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_log() + elif args.command == "restore": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_restore(args.version) + +if __name__ == "__main__": + # print("\n=== dsi-vcs: rsync-based file version control ===\n") + # print(os.getcwd()) + main() diff --git a/tools/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py similarity index 88% rename from tools/version_control/dsi_vcs.py rename to dsi/utils/version_control/dsi_vcs.py index 53901553..bc30538a 100755 --- a/tools/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -26,13 +26,12 @@ import subprocess import json import datetime -import argparse import shutil from pathlib import Path from typing import Optional -from vcs_db import * -from vcs_metadata_helper import * +from .vcs_db import * +from .vcs_metadata_helper import * # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -507,69 +506,3 @@ def cmd_restore(self, commit_hash: str): if result.returncode not in (0, 24): sys.exit(f"rsync restore failed:\n{result.stderr}") print(f" Restored to {full_hash}") - - -# ─────────────────────────── CLI ───────────────────────────────────────────── - -def main(): - parser = argparse.ArgumentParser( - description="dsi_vcs — rsync-based file version control with full Linux metadata" - ) - sub = parser.add_subparsers(dest="command", required=True) - - p_init = sub.add_parser("init", help="Initialize a new repo") - # p_init.add_argument("root_folder") - - p_add = sub.add_parser("add", help="add file or directory to staging") - p_add.add_argument('files', nargs='+', type=str) - - p_delete = sub.add_parser("delete", help="stage file or directory deletion") - p_delete.add_argument('files', nargs='+', type=str) - - p_remove = sub.add_parser("remove", help="remove file or directory from staging") - p_remove.add_argument('files', nargs='+', type=str) - - p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") - p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) - - p_log = sub.add_parser("log", help="List all commits") - - p_diff = sub.add_parser("diff", help="Diff two commits") - p_diff.add_argument("c1") - p_diff.add_argument("c2") - - p_restore = sub.add_parser("restore", help="Restore a version") - p_restore.add_argument("version") - - args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) - - # parser.print_help() - # parser.format_help() - if args.command == "init": - vcs = DSIVCS(os.getcwd()) - elif args.command == "add": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_add(args.files) - elif args.command == "delete": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_delete(args.files) - elif args.command == "remove": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_remove(args.files) - elif args.command == "commit": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_commit(args.message) - elif args.command == "diff": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_diff(args.c1, args.c2) - elif args.command == "log": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_log() - elif args.command == "restore": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_restore(args.version) - -if __name__ == "__main__": - # print("\n=== dsi-vcs: rsync-based file version control ===\n") - # print(os.getcwd()) - main() diff --git a/tools/version_control/requirements.txt b/dsi/utils/version_control/requirements.txt similarity index 100% rename from tools/version_control/requirements.txt rename to dsi/utils/version_control/requirements.txt diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py new file mode 100644 index 00000000..c1b961ba --- /dev/null +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -0,0 +1,70 @@ +import os +import sqlite3 +from pathlib import Path +from shutil import which +import pytest +import stat + +from dsi.utils.version_control.dsi_vcs import DSIVCS +from dsi.utils.version_control.vcs_db import DB_NAME, SNAPSHOTS_DIR + +def require_rsync(): + if which("rsync") is None: + pytest.skip("rsync is required for dsi_vcs.") + + + +def connect_repo(repo_path): + return sqlite3.connect(repo_path / SNAPSHOTS_DIR / DB_NAME) + +def commits(repo_path): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return conn.execute( + "SELECT id, commit_hash, snapshot_path FROM versions ORDER BY id" + ).fetchall() + +def latest_entries(repo_path): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return conn.execute( + "SELECT relative_path, file_type, permissions_octal, permissions_str, setgid " + "FROM file_entries WHERE version_id=(SELECT MAX(id) from versions) " + "ORDER BY relative_path").fetchall() + +def test_add(tmp_path): + require_rsync() + repo = DSIVCS(str(tmp_path)) + + empty_dir = tmp_path / "empty" + nested_dir = tmp_path / "nested" / "child" + empty_dir.mkdir() + nested_dir.mkdir(parents=True) + (tmp_path / "a.txt").write_text("foo") + (nested_dir / "b.txt").write_text("bar") + + empty_dir.chmod(0o2775) + + repo.cmd_add(["a.txt", "nested", "empty"]) + repo.cmd_commit("alpha") + + (tmp_path / "c.txt").write_text("baz") + + repo.cmd_add(["c.txt"]) + repo.cmd_commit("beta") + + alpha, beta = commits(tmp_path) + alpha_path = Path(alpha["snapshot_path"]) + beta_path = Path(beta["snapshot_path"]) + assert (alpha_path / "a.txt").read_text() == "foo" + assert (alpha_path / "nested" / "child" / "b.txt").read_text() == "bar" + assert (beta_path / "c.txt").read_text() == "baz" + assert (alpha_path / "empty").exists() + assert stat.S_IMODE((alpha_path / "empty").stat().st_mode) == 0o2775 + + rows = {row["relative_path"]: row for row in latest_entries(tmp_path)} + empty_entry = rows["empty"] + assert empty_entry["file_type"] == "dir" + assert empty_entry["permissions_str"] == "rwxrwsr-x" + assert eval(empty_entry["permissions_octal"]) == 0o2775 + assert empty_entry["setgid"] == 1 diff --git a/tools/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py similarity index 100% rename from tools/version_control/vcs_db.py rename to dsi/utils/version_control/vcs_db.py diff --git a/tools/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py similarity index 99% rename from tools/version_control/vcs_metadata_helper.py rename to dsi/utils/version_control/vcs_metadata_helper.py index a8734f7f..9b756f3b 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -9,6 +9,8 @@ import sys from typing import Optional +from .vcs_db import DB_NAME, SNAPSHOTS_DIR + # ─────────────────────────── METADATA HELPERS ──────────────────────────────── def file_type_str(mode: int) -> str: diff --git a/pyproject.toml b/pyproject.toml index 13424634..d82e6a2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,8 +29,9 @@ include-package-data = true [project.scripts] dsi = "dsi.cli:main" +dsi-vcs = "dsi.utils.version_control.cli:main" [tool.setuptools.packages.find] where = ["."] include = ["dsi*"] -exclude = ["clover3d"] \ No newline at end of file +exclude = ["clover3d"] From da9b15f3ba5ee971d56bb9dbc8f95a2554e7fd62 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Fri, 24 Apr 2026 12:33:22 -0600 Subject: [PATCH 026/109] VCS: begin stubbing vcs interface in core. --- dsi/core.py | 36 ++++++++++++++++++++++++++++++ dsi/tests/test_core.py | 50 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/dsi/core.py b/dsi/core.py index f19c6c92..154a92a3 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1445,3 +1445,39 @@ def can_create_file_here(self, dir = "."): return True except (PermissionError, OSError): return False + + def vcs_init(self, repo_path): + """Initialize VCS repo.""" + pass + + def vcs_status(self): + """Return vcs status metadata for the first loaded backend.""" + pass + + def vcs_commit(self, message, author_name=None, author_email=None): + """Commit the current versioned backend state.""" + pass + + def vcs_diff(self, old_ref=None, new_ref=None, cached=False): + """Return the git diff for the first loaded backend.""" + pass + + def vcs_log(self, limit=None, ref='HEAD'): + """Return commit history for the first loaded backend.""" + pass + + def vcs_current_branch(self): + """Return the current branch for the first loaded backend.""" + pass + + def vcs_list_branches(self): + """Return local branches for the first loaded backend.""" + pass + + def vcs_create_branch(self, name, start_point='HEAD', checkout=False, force=False): + """Create a branch on the first loaded backend.""" + pass + + def vcs_checkout_branch(self, name, force=False): + """Checkout a branch on the first loaded backend.""" + pass diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index 6b8dd2c1..1c69ffe1 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -21,6 +21,54 @@ def test_unload_module(): a.unload_module('plugin', 'GitInfo', 'writer') assert len(a.list_loaded_modules()['writer']) == 0 +def test_sqlite_versioning(tmp_path): + a = Terminal() + dbpath = tmp_path / "core_versioned.db" + # dbpath = '/tmp/versionined.db' + + a.load_module( + 'plugin', + 'Dict', + 'reader', + collection=OrderedDict({"wildfire": OrderedDict({'foo': [1], 'bar': [3]})}), + ) + a.load_module('backend', 'Sqlite', 'back-write', filename=str(dbpath)) + a.artifact_handler(interaction_type='ingest') + + a.vcs_init(tmp_path) + + # print(a.vcs_current_branch()) + # print(a.vcs_status()) + # status = a.vcs_status() + # assert status['branch'] == 'main' + # assert status['is_dirty'] is True + + # commit_id = a.vcs_commit('initial snapshot') + # assert len(commit_id) == 40 + # assert a.vcs_status()['is_dirty'] is False + # assert a.vcs_log(limit=1)[0]['message'] == 'initial snapshot' + # print(a.vcs_log()) + + # a.vcs_create_branch('feature', checkout=True) + # assert a.vcs_current_branch() == 'feature' + # print(a.vcs_list_branches()) + + # a.update_abstraction('wildfire', OrderedDict({'foo': [1, 2], 'bar': [3, 4]})) + # a.artifact_handler(interaction_type='ingest') + # diff = a.vcs_diff() + # print(diff) + # assert '+INSERT INTO \"wildfire\" VALUES(2,4);' in diff + + # a.vcs_commit('feature snapshot') + + # a.vcs_checkout_branch('main') + # table = a.get_table('wildfire') + # assert table.values.tolist() == [[1, 3]] + # assert sorted(a.vcs_list_branches()) == ['feature', 'main'] + + a.close() + + # SQLITE TESTS def test_ingest_sqlite_backend(): a = Terminal() @@ -1400,4 +1448,4 @@ def test_terminal_ndp_close(): # After close assert backend._loaded is False - assert len(backend._cache) == 0 \ No newline at end of file + assert len(backend._cache) == 0 From e70211539accf15902473e259c4645ba0bb34827 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 09:44:02 -0600 Subject: [PATCH 027/109] VCS:update diff and modifeid version class --- dsi/dsi.py | 70 ++++++++++++++++++- dsi/tests/test_dsi.py | 27 ++++++- dsi/utils/version_control/__init__.py | 4 +- dsi/utils/version_control/cli.py | 23 +++--- dsi/utils/version_control/dsi_vcs.py | 51 +++++++++++--- dsi/utils/version_control/tests/test_vcs.py | 28 +++++++- dsi/utils/version_control/vcs_db.py | 2 +- .../version_control/vcs_metadata_helper.py | 53 +++++++------- 8 files changed, 207 insertions(+), 51 deletions(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index 45d6514e..f825c127 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -16,6 +16,8 @@ import inspect import warnings + +from dsi.utils.version_control.dsi_vcs import Version warnings.filterwarnings("ignore", category=FutureWarning) logger = logging.getLogger(__name__) @@ -201,7 +203,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) msg = f"Created an instance of DSI with the {backend_name} backend: {filename}" else: msg = "Created an instance of DSI" - + self.vcs = None logger.log(logging.INFO, msg) if self.silence_messages else print(msg) @@ -1277,3 +1279,69 @@ def move(self, filepath): def fetch(self, fname): pass + + def version(self, command: str, args: str = None): + """ + Internal DSI Versioning. + + `command` : str + - init # initialize a versioning repository in a root folder + - add # add file(s) to the staging area for the next commit + - remove # remove file(s) from the staging area without touching the actual files + - delete # delete file(s) from the staging area for the next commit + - commit # commit a new version with the staged file(s) and an optional message describing the version + - log # list versions + - diff # diff between two versions. If no version is provided, diff the current version with the previous version + - restore # restore a version with commit hash + + `args` : str + - init: A required argument with the name of the root folder for the versioning repository. + - add: A required argument with the file(s) to add to the staging area for the next commit, specified as a space-separated string or list of file paths. + - remove: A required argument with the file(s) to remove from the staging area without touching the actual files, specified as a space-separated string or list of file paths. + - delete: A required argument with the file(s) to delete from the staging area for the next commit, specified as a space-separated string or list of file paths. + - commit: An optional message describing the version being committed, specified as a string. + - log: An optional argument to specify the number of recent versions to display, specified as an integer. If not provided, recent 5 versions are displayed. + - diff: An optional argument to specify the versions to compare, specified as a space-separated string or list of commit hashes. + - restore: A required argument to specify the version to restore, specified by its commit hash. + """ + print("hello from versioning") + if command == "init": + if args is None: + raise RuntimeError("version() ERROR: 'init' command requires a 'root_folder' argument specifying the name of the root folder for the versioning repository.") + + self.vcs = Version(args) + elif command == "add" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'add' command requires a 'files' argument specifying the file(s) to add to the staging area for the next commit.") + print("-->" + self.vcs.root_folder) + self.vcs.cmd_add(args.split()) + elif command == "remove" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'remove' command requires a 'files' argument specifying the file(s) to remove from the staging area for the next commit.") + self.vcs.cmd_remove(args.split()) + elif command == "delete" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'delete' command requires a 'files' argument specifying the file(s) to delete from the repository.") + self.vcs.cmd_delete(args.split()) + elif command == "commit" and self.vcs is not None: + self.vcs.cmd_commit(args) + elif command == "diff" and self.vcs is not None: + c1 = None + c2 = None + if args is not None: + arg_list = args.split() + if len(arg_list) == 1: + c1 = arg_list[0] + elif len(arg_list) == 2: + c1, c2 = arg_list + elif len(arg_list) > 2: + raise RuntimeError("version() ERROR: 'diff' command requires zero, one, or two commit hashes as arguments.") + self.vcs.cmd_diff(c1, c2) + elif command == "log" and self.vcs is not None: + self.vcs.cmd_log() + elif command == "restore" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'restore' command requires a 'commit_hash' argument specifying the version to restore.") + self.vcs.cmd_restore(args) + else: + raise RuntimeError("version() ERROR: Invalid command or versioning repository not initialized. Please check the command and ensure 'init' has been called with a root folder argument.") \ No newline at end of file diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 77cd6b8a..34f9e4a5 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1865,4 +1865,29 @@ def test_close_ndp_backend(): assert df is not None dsi.close() - assert True \ No newline at end of file + assert True + +def main(): +# def test_versioning(): + test = DSI() + # test.version("init", os.getcwd()) + wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + # wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + test.version("add", os.path.join(wpath, "a_dummy_file")) + print(">Single file added.") + test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) + print(">Multi file added.") + print(">Versioning initialized.") + + test.version("remove", os.path.join(wpath, "schema2.json")) + print(">Single file removed.") + + test.version("commit", "Tester Commits") + test.version("log") + assert True + +if __name__ == "__main__": + main() diff --git a/dsi/utils/version_control/__init__.py b/dsi/utils/version_control/__init__.py index 64b89704..d22f487d 100644 --- a/dsi/utils/version_control/__init__.py +++ b/dsi/utils/version_control/__init__.py @@ -1,3 +1,3 @@ -from .dsi_vcs import DSIVCS +from .dsi_vcs import Version -__all__ = ["DSIVCS"] +__all__ = ["Version"] diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py index 97823901..50b801f4 100644 --- a/dsi/utils/version_control/cli.py +++ b/dsi/utils/version_control/cli.py @@ -1,8 +1,9 @@ import argparse import datetime import sys +import os -from .dsi_vcs import DSIVCS +from .dsi_vcs import Version def main(): parser = argparse.ArgumentParser( @@ -28,8 +29,8 @@ def main(): p_log = sub.add_parser("log", help="List all commits") p_diff = sub.add_parser("diff", help="Diff two commits") - p_diff.add_argument("c1") - p_diff.add_argument("c2") + p_diff.add_argument("c1", nargs="?", default=None) + p_diff.add_argument("c2", nargs="?", default=None) p_restore = sub.add_parser("restore", help="Restore a version") p_restore.add_argument("version") @@ -39,27 +40,27 @@ def main(): # parser.print_help() # parser.format_help() if args.command == "init": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) elif args.command == "add": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_add(args.files) elif args.command == "delete": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_delete(args.files) elif args.command == "remove": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_remove(args.files) elif args.command == "commit": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_commit(args.message) elif args.command == "diff": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_diff(args.c1, args.c2) elif args.command == "log": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_log() elif args.command == "restore": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_restore(args.version) if __name__ == "__main__": diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index bc30538a..4c20c1a3 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -91,7 +91,7 @@ def apply_snapshot_deletes(snapshot_path: str, root_folder: str, staged_deletes: # ─────────────────────────── COMMANDS ──────────────────────────────────────── -class DSIVCS(): +class Version(): def __init__(self, folder: str): self.root_folder = os.path.abspath(folder) @@ -411,16 +411,36 @@ def cmd_diff(self, c1: str, c2: str): root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) + def get_files_in_root_folder(): + files = {} + for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in {DB_NAME, SNAPSHOTS_DIR}] + for fname in filenames: + if fname not in {DB_NAME, SNAPSHOTS_DIR}: + abs_path = os.path.join(dirpath, fname) + rel_path = os.path.relpath(abs_path, root_folder) + files[rel_path] = collect_metadata(abs_path, self.root_folder) + files[rel_path]["absolute_path"] = abs_path + files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} + return files + def get_files(chash): - vid = conn.execute( - "SELECT id FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", - (root_folder, chash + "%") - ).fetchone() + vid = None + if chash == "latest": + vid = conn.execute( + "SELECT id, commit_hash FROM versions WHERE root_folder=? ORDER BY id DESC LIMIT 1", + (root_folder,) + ).fetchone() + else: + vid = conn.execute( + "SELECT id, commit_hash FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, chash + "%") + ).fetchone() if not vid: conn.close() sys.exit(f"Commit '{chash}' not found.") rows = conn.execute( - "SELECT relative_path, file_type, md5_hash, permissions_octal, " + "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_octal, " " owner_name, group_name, lstat " "FROM file_entries WHERE version_id=?", (vid["id"],) ).fetchall() @@ -428,12 +448,25 @@ def get_files(chash): for r in rows: rec = dict(r) # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. + snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, vid["commit_hash"][:12]) + rec["absolute_path"] = snapshot_target(snapshot_path, r["relative_path"]) rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} result[r["relative_path"]] = rec return result - files1 = get_files(c1) - files2 = get_files(c2) + files1 = files2 = {} + if c1 == None and c2 == None: + files1 = get_files_in_root_folder() + files2 = get_files("latest") + elif c1 == None: + files1 = get_files_in_root_folder() + files2 = get_files(c2) + elif c2 == None: + files1 = get_files(c1) + files2 = get_files_in_root_folder() + else: + files1 = get_files(c1) + files2 = get_files(c2) conn.close() all_paths = sorted(set(files1) | set(files2)) @@ -461,6 +494,8 @@ def get_files(chash): changes = [] if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: changes.append("content") + result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) + print(f"diff result: {result.stdout.strip()}") if f1["permissions_octal"] != f2["permissions_octal"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index c1b961ba..47a73edb 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -5,7 +5,8 @@ import pytest import stat -from dsi.utils.version_control.dsi_vcs import DSIVCS +from dsi.dsi import DSI +from dsi.utils.version_control.dsi_vcs import Version from dsi.utils.version_control.vcs_db import DB_NAME, SNAPSHOTS_DIR def require_rsync(): @@ -34,7 +35,7 @@ def latest_entries(repo_path): def test_add(tmp_path): require_rsync() - repo = DSIVCS(str(tmp_path)) + repo = Version(str(tmp_path)) empty_dir = tmp_path / "empty" nested_dir = tmp_path / "nested" / "child" @@ -68,3 +69,26 @@ def test_add(tmp_path): assert empty_entry["permissions_str"] == "rwxrwsr-x" assert eval(empty_entry["permissions_octal"]) == 0o2775 assert empty_entry["setgid"] == 1 + + +def test_versioning(): + test = DSI() + # test.version("init", os.getcwd()) + # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + test.version("add", os.path.join(wpath, "a_dummy_file")) + print(">Single file added.") + test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) + print(">Multi file added.") + print(">Versioning initialized.") + + test.version("remove", os.path.join(wpath, "schema2.json")) + print(">Single file removed.") + + test.version("commit", "Tester Commits") + test.version("log") + test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") + assert True diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index aaf21fca..c5e849fa 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS staging ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, - absolute_path TEXT NOT NULL, + absolute_path TEXT NOT NULL UNIQUE, -- absolute path must be unique in staging action TEXT NOT NULL DEFAULT 'add', added_at TEXT NOT NULL, -- ISO-8601 timestamp UNIQUE(root_folder, absolute_path) diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index 9b756f3b..ec39915c 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -76,12 +76,14 @@ def get_acl(path: str) -> Optional[str]: acl_entries.append(acl_part) return ";".join(acl_entries) if acl_entries else None - else: + elif sys.platform == "linux": result = subprocess.run( ["getfacl", "--omit-header", "--absolute-names", path], capture_output=True, text=True, timeout=5 ) return result.stdout.strip() or None + else: + return None except (FileNotFoundError, subprocess.TimeoutExpired): return None @@ -107,29 +109,30 @@ def set_acl(path: str, acl_string: str) -> Optional[str]: def get_xattrs(path: str) -> Optional[str]: - """Return JSON dict of extended attributes, or None.""" - try: - import xattr # pyxattr - attrs = {} - for key in xattr.listxattr(path, symlink=True): - try: - val = xattr.getxattr(path, key, symlink=True) - attrs[key] = val.decode("utf-8", errors="replace") - except OSError: - attrs[key] = "" - return json.dumps(attrs) if attrs else None - except ImportError: - # Fallback: use getfattr - try: - result = subprocess.run( - ["getfattr", "-d", "-m", "-", "--absolute-names", path], - capture_output=True, text=True, timeout=5 - ) - return result.stdout.strip() or None - except FileNotFoundError: - return None - except OSError: - return None + return None + # """Return JSON dict of extended attributes, or None.""" + # try: + # import xattr # pyxattr + # attrs = {} + # for key in xattr.listxattr(path, symlink=True): + # try: + # val = xattr.getxattr(path, key, symlink=True) + # attrs[key] = val.decode("utf-8", errors="replace") + # except OSError: + # attrs[key] = "" + # return json.dumps(attrs) if attrs else None + # except ImportError: + # # Fallback: use getfattr + # try: + # result = subprocess.run( + # ["getfattr", "-d", "-m", "-", "--absolute-names", path], + # capture_output=True, text=True, timeout=5 + # ) + # return result.stdout.strip() or None + # except FileNotFoundError: + # return None + # except OSError: + # return None def get_security_context(path: str) -> Optional[str]: @@ -218,7 +221,7 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: # external / subprocess-derived attributes "acl_text": get_acl(abs_path), - "xattrs": get_xattrs(abs_path), + # "xattrs": get_xattrs(abs_path), "security_context": get_security_context(abs_path), "symlink_target": os.readlink(abs_path) if ftype == "symlink" else None, From 870c099c118204d28b2ffe9bd63d3d25787981a5 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 10:52:13 -0600 Subject: [PATCH 028/109] VCS: update versioning tests --- dsi/tests/test_dsi.py | 11 ++++------- dsi/utils/version_control/tests/test_vcs.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 34f9e4a5..563307b4 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1867,12 +1867,11 @@ def test_close_ndp_backend(): assert True -def main(): -# def test_versioning(): +def test_versioning(): test = DSI() # test.version("init", os.getcwd()) - wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - # wpath = os.getcwd() + # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + wpath = os.getcwd() test.version("init", wpath) assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") @@ -1887,7 +1886,5 @@ def main(): test.version("commit", "Tester Commits") test.version("log") + test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") assert True - -if __name__ == "__main__": - main() diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 47a73edb..6b7ba913 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -90,5 +90,5 @@ def test_versioning(): test.version("commit", "Tester Commits") test.version("log") - test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") + test.version("diff") assert True From 53e30ffcc410fa08799cc68656c16ad046384ef2 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 11:44:34 -0600 Subject: [PATCH 029/109] VCS: add readme file --- dsi/utils/version_control/README.md | 335 ++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 dsi/utils/version_control/README.md diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md new file mode 100644 index 00000000..8b5dd0ea --- /dev/null +++ b/dsi/utils/version_control/README.md @@ -0,0 +1,335 @@ +# dsi-vcs: rsync-based File Version Control with Full Linux Metadata + +## Overview + +**dsi-vcs** is a lightweight file version control system designed for capturing and preserving complete Linux file metadata. Unlike traditional VCS tools, dsi-vcs focuses on: + +- **Full metadata capture**: permissions, ownership, ACLs, extended attributes, and SELinux contexts +- **Rsync-based snapshots**: efficient storage with hard-link deduplication +- **SQLite database**: structured metadata storage for querying and diffing +- **Complete file history**: MD5 hashes, file stats, and all metadata changes tracked + +--- + +## CLI Commands + +Initialize dsi-vcs in the current directory: + +```bash +dsi-vcs init +``` + +### Stage Files for Commit + +**Add files/directories:** + +```bash +dsi-vcs add [ ...] +dsi-vcs add ./data +dsi-vcs add file1.txt file2.txt +``` + +**Stage files for deletion:** + +```bash +dsi-vcs delete [ ...] +dsi-vcs delete ./old_data/ +``` + +**Remove files from staging (without deleting):** + +```bash +dsi-vcs remove [ ...] +dsi-vcs remove file1.txt +``` + +### Commit Changes + +Create a snapshot with an optional message: + +```bash +dsi-vcs commit +dsi-vcs commit "Initial data import" +``` + +### View History + +List all commits: + +```bash +dsi-vcs log +``` + +Example output: + +```shell +COMMIT HASH OWNER DATE/TIME (UTC) FILES BYTES MESSAGE +──────────────────────────────────────────────────────────────────────────────────────────────────── +f826177ae78f4e48a8c08054e2bb9a71 owner1 2026-04-28T21:10:05.339321+00:00 7 15,557 first commit +4af9e3d4dc854d699b96b5a84f913ac0 owner2 2026-04-28T21:19:47.167081+00:00 7 15,559 second commit +``` + +### Compare Versions + +Diff two commits (shows added, modified, deleted files). + +Parameters: + +- Provide upto two commit hash as paramerter. +- No parameter will compare with the latest changes with the last commit. +- One parameter will compare with the latest change with the provided commit version. +- Two parameters will compare between the provided versions. + +```bash +dsi-vcs diff +dsi-vcs diff f826177ae78f4e48a8c08054e2bb9a71 +``` + +Example output: + +```shell +Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) + +STATUS PATH +────────────────────────────────────────────────────────────────────── +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + +Summary: +0 added -0 deleted ~3 modified =4 unchanged +``` + +### Restore a Version + +Restore the entire repository to a previous commit: + +```bash +dsi-vcs restore +dsi-vcs restore abc123def456 +``` + +## Python API + +Integrated within DSI. User versioning with the function `version(command, args)` + +### Usage + +```python +obj.version(command: str, args: str = None) +``` + +### Parameters + +| Parameter | Type | Description | +| ----------- | ------- | -------------------------------------------------------- | +| `command` | `str` | The versioning operation to perform (see commands below) | +| `args` | `str` | Optional or required arguments depending on the command | + +--- + +### `init` + +Initializes a versioning repository in a root folder. + +```python +obj.version("init", "my_project_folder") +``` + +**Args (required):** Name of the root folder for the versioning repository. + +--- + +### `add` + +Adds one or more files to the staging area for the next commit. + +```python +obj.version("add", "file1.py file2.py") +``` + +**Args (required):** Space-separated file paths to stage. + +--- + +### `remove` + +Removes one or more files from the staging area **without** deleting the actual files. + +```python +obj.version("remove", "file1.py") +``` + +**Args (required):** Space-separated file paths to unstage. + +--- + +### `delete` + +Marks one or more files for deletion in the next commit. + +```python +obj.version("delete", "file1.py file2.py") +``` + +**Args (required):** Space-separated file paths to delete. + +--- + +### `commit` + +Commits all staged changes as a new version snapshot. + +```python +obj.version("commit", "Initial release") # with message +obj.version("commit") # without message +``` + +**Args (optional):** A descriptive message for the version being committed. + +--- + +### `log` + +Lists recent committed versions. + +```python +obj.version("log") # shows last 5 versions (default) +obj.version("log", "10") # shows last 10 versions +``` + +**Args (optional):** Number of recent versions to display. Defaults to `5`. + +--- + +### `diff` + +Shows the differences between two versions. + +```python +obj.version("diff") # current vs previous +obj.version("diff", "abc123") # specific commit vs its previous +obj.version("diff", "abc123 def456") # between two specific commits +``` + +**Args (optional):** Zero, one, or two commit hashes separated by a space. + +| # of hashes | Behavior | +| ----------- | -------------------------------------------------- | +| 0 | Diffs current version against previous | +| 1 | Diffs specified commit against its previous | +| 2 | Diffs the two specified commits against each other | + +--- + +### `restore` + +Restores the repository to a previously committed version. + +```python +obj.version("restore", "abc123def456") +``` + +**Args (required):** The commit hash of the version to restore. + +Example : Basic Workflow + +```python +from dsi.utils.version_control import Version + +# Initialize dsi +dsi = DSI() + +# Initialize repo +dsi.version("init", "/data/archive") + +# Stage files +dsi.version("add", ["./documents", "config.json"]) + +# Commit +dsi.version("commit", "Initial archive") + +# View history +dsi.version("log") + +# Modify files and commit again +dsi.version(["./documents"]) +dsi.version("commit", "Updated documents") + +# Compare two versions +dsi.version("diff") +``` + +## Database Schema + +### `versions` Table + +| Column | Type | Description | +| ------------- | ---------- | --------------------------- | +| id | INTEGER PK | Auto-increment ID | +| root_folder | TEXT | Repository root path | +| commit_hash | TEXT | UUID4 hex (32 chars) | +| committed_at | TEXT | ISO-8601 timestamp | +| owner_name | TEXT | Linux user who committed | +| message | TEXT | Optional commit message | +| snapshot_path | TEXT | Path to rsync snapshot copy | +| file_count | INTEGER | Number of files in commit | +| total_bytes | INTEGER | Total size in bytes | + +### `file_entries` Table + +Stores metadata for each file in each commit. + +| Column | Type | Description | +| ---------------------- | ---------- | -------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| absolute_path | TEXT | Full path | +| file_name | TEXT | Filename only | +| file_type | TEXT | file/dir/symlink/etc | +| md5_hash | TEXT | Content hash (files only) | +| lstat | TEXT | JSON of os.lstat() result | +| permissions_octal | TEXT | e.g., "0o755" | +| permissions_str | TEXT | e.g., "rwxr-xr-x" | +| owner_name | TEXT | Username | +| group_name | TEXT | Group name | +| setuid, setgid, sticky | INTEGER | Special bits (0/1) | +| acl_text | TEXT | Raw getfacl output | +| xattrs | TEXT | JSON dict of extended attributes | +| security_context | TEXT | SELinux context | +| symlink_target | TEXT | Target of symlink | + +### `staging` Table + +Temporary storage for files to be committed. + +| Column | Type | Description | +| ------------- | ---------- | ----------------------- | +| id | INTEGER PK | Auto-increment | +| root_folder | TEXT | Partition key | +| absolute_path | TEXT | Full file path (UNIQUE) | +| action | TEXT | "add" or "delete" | +| added_at | TEXT | ISO-8601 timestamp | + +--- + +## Directory Structure + +``` +root_folder/ +└── .dsi_vcs_snapshots/ # rsync snapshot directory + ├── .dsi_vcs.db # SQLite metadata database + ├── abc123def456/ # Snapshot for commit abc123def456 + ├── xyz789abc123/ # Snapshot for commit xyz789abc123 + └── ... +``` From 0b5ed7240d738c60bda84dde26dc8fab90034f17 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 11:59:34 -0600 Subject: [PATCH 030/109] VCS: fix linter errors --- dsi/utils/version_control/cli.py | 4 +-- dsi/utils/version_control/dsi_vcs.py | 11 +++--- .../version_control/vcs_metadata_helper.py | 34 ++++++++++++------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py index 50b801f4..06007da2 100644 --- a/dsi/utils/version_control/cli.py +++ b/dsi/utils/version_control/cli.py @@ -11,7 +11,7 @@ def main(): ) sub = parser.add_subparsers(dest="command", required=True) - p_init = sub.add_parser("init", help="Initialize a new repo") + sub.add_parser("init", help="Initialize a new repo") # p_init.add_argument("root_folder") p_add = sub.add_parser("add", help="add file or directory to staging") @@ -26,7 +26,7 @@ def main(): p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) - p_log = sub.add_parser("log", help="List all commits") + sub.add_parser("log", help="List all commits") p_diff = sub.add_parser("diff", help="Diff two commits") p_diff.add_argument("c1", nargs="?", default=None) diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 4c20c1a3..67ea3965 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -27,11 +27,10 @@ import json import datetime import shutil -from pathlib import Path from typing import Optional -from .vcs_db import * -from .vcs_metadata_helper import * +from .vcs_db import DB_NAME, SNAPSHOTS_DIR, open_db +from .vcs_metadata_helper import collect_metadata, collect_tree_metadata, owner_name # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -455,13 +454,13 @@ def get_files(chash): return result files1 = files2 = {} - if c1 == None and c2 == None: + if c1 is None and c2 is None: files1 = get_files_in_root_folder() files2 = get_files("latest") - elif c1 == None: + elif c1 is None: files1 = get_files_in_root_folder() files2 = get_files(c2) - elif c2 == None: + elif c2 is None: files1 = get_files(c1) files2 = get_files_in_root_folder() else: diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index ec39915c..8bc4da7c 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -1,7 +1,6 @@ import os import stat import hashlib -import sqlite3 import json import subprocess import pwd @@ -14,13 +13,20 @@ # ─────────────────────────── METADATA HELPERS ──────────────────────────────── def file_type_str(mode: int) -> str: - if stat.S_ISREG(mode): return "file" - if stat.S_ISDIR(mode): return "dir" - if stat.S_ISLNK(mode): return "symlink" - if stat.S_ISBLK(mode): return "block" - if stat.S_ISCHR(mode): return "char" - if stat.S_ISFIFO(mode): return "fifo" - if stat.S_ISSOCK(mode): return "socket" + if stat.S_ISREG(mode): + return "file" + if stat.S_ISDIR(mode): + return "dir" + if stat.S_ISLNK(mode): + return "symlink" + if stat.S_ISBLK(mode): + return "block" + if stat.S_ISCHR(mode): + return "char" + if stat.S_ISFIFO(mode): + return "fifo" + if stat.S_ISSOCK(mode): + return "socket" return "unknown" @@ -44,13 +50,17 @@ def permission_str(mode: int) -> str: def owner_name(uid: int) -> str: - try: return pwd.getpwuid(uid).pw_name - except KeyError: return str(uid) + try: + return pwd.getpwuid(uid).pw_name + except KeyError: + return str(uid) def group_name(gid: int) -> str: - try: return grp.getgrgid(gid).gr_name - except KeyError: return str(gid) + try: + return grp.getgrgid(gid).gr_name + except KeyError: + return str(gid) def get_acl(path: str) -> Optional[str]: From 1cf9181ad75bf1dc4e58c4521aa534ffdad31b53 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 12:13:30 -0600 Subject: [PATCH 031/109] VCS: fix version testers --- dsi/tests/test_core.py | 48 ---------------------------------- dsi/tests/test_dsi.py | 58 ++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index 1c69ffe1..4d755442 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -21,54 +21,6 @@ def test_unload_module(): a.unload_module('plugin', 'GitInfo', 'writer') assert len(a.list_loaded_modules()['writer']) == 0 -def test_sqlite_versioning(tmp_path): - a = Terminal() - dbpath = tmp_path / "core_versioned.db" - # dbpath = '/tmp/versionined.db' - - a.load_module( - 'plugin', - 'Dict', - 'reader', - collection=OrderedDict({"wildfire": OrderedDict({'foo': [1], 'bar': [3]})}), - ) - a.load_module('backend', 'Sqlite', 'back-write', filename=str(dbpath)) - a.artifact_handler(interaction_type='ingest') - - a.vcs_init(tmp_path) - - # print(a.vcs_current_branch()) - # print(a.vcs_status()) - # status = a.vcs_status() - # assert status['branch'] == 'main' - # assert status['is_dirty'] is True - - # commit_id = a.vcs_commit('initial snapshot') - # assert len(commit_id) == 40 - # assert a.vcs_status()['is_dirty'] is False - # assert a.vcs_log(limit=1)[0]['message'] == 'initial snapshot' - # print(a.vcs_log()) - - # a.vcs_create_branch('feature', checkout=True) - # assert a.vcs_current_branch() == 'feature' - # print(a.vcs_list_branches()) - - # a.update_abstraction('wildfire', OrderedDict({'foo': [1, 2], 'bar': [3, 4]})) - # a.artifact_handler(interaction_type='ingest') - # diff = a.vcs_diff() - # print(diff) - # assert '+INSERT INTO \"wildfire\" VALUES(2,4);' in diff - - # a.vcs_commit('feature snapshot') - - # a.vcs_checkout_branch('main') - # table = a.get_table('wildfire') - # assert table.values.tolist() == [[1, 3]] - # assert sorted(a.vcs_list_branches()) == ['feature', 'main'] - - a.close() - - # SQLITE TESTS def test_ingest_sqlite_backend(): a = Terminal() diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 563307b4..aebee5a1 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -6,6 +6,8 @@ from pandas import DataFrame from collections import OrderedDict import hashlib +import random +import tempfile def test_list_functions(): test = DSI() @@ -1795,6 +1797,40 @@ def test_fail_overwrite_schema_duckdb_backend(): except Exception as e: expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." assert str(e) == expected + + +def test_versioning(): + test = DSI() + wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + # Create a file with ten random integers + dummy_file_path = os.path.join(wpath, "a_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "a_dummy_file")) + + dummy_file_path = os.path.join(wpath, "second_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "second_dummy_file")) + + dummy_file_path = os.path.join(wpath, "third_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "third_dummy_file")) + + test.version("remove", os.path.join(wpath, "second_dummy_file")) + test.version("delete", os.path.join(wpath, "third_dummy_file")) + + test.version("commit", "Tester Commits") + test.version("log") + test.version("diff") + assert True # NDP @@ -1866,25 +1902,3 @@ def test_close_ndp_backend(): dsi.close() assert True - -def test_versioning(): - test = DSI() - # test.version("init", os.getcwd()) - # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - wpath = os.getcwd() - test.version("init", wpath) - assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") - - test.version("add", os.path.join(wpath, "a_dummy_file")) - print(">Single file added.") - test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) - print(">Multi file added.") - print(">Versioning initialized.") - - test.version("remove", os.path.join(wpath, "schema2.json")) - print(">Single file removed.") - - test.version("commit", "Tester Commits") - test.version("log") - test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") - assert True From 4f12c2ab685519575ba22f932d5e884053a98ecb Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 12:15:02 -0600 Subject: [PATCH 032/109] VCS: remvoed unused import --- dsi/tests/test_dsi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index aebee5a1..cded35bd 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -7,7 +7,6 @@ from collections import OrderedDict import hashlib import random -import tempfile def test_list_functions(): test = DSI() From 5d8e85fdf9beb5dd4929419808c5b9d28430c9ce Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 14 May 2026 10:33:58 -0600 Subject: [PATCH 033/109] VCS: readme user text update --- dsi/dsi.py | 1 - dsi/utils/version_control/README.md | 32 ++++++++++++++--------------- dsi/utils/version_control/vcs_db.py | 2 +- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index f825c127..52014242 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -1304,7 +1304,6 @@ def version(self, command: str, args: str = None): - diff: An optional argument to specify the versions to compare, specified as a space-separated string or list of commit hashes. - restore: A required argument to specify the version to restore, specified by its commit hash. """ - print("hello from versioning") if command == "init": if args is None: raise RuntimeError("version() ERROR: 'init' command requires a 'root_folder' argument specifying the name of the root folder for the versioning repository.") diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index 8b5dd0ea..aa5ec8eb 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -89,22 +89,22 @@ Example output: ```shell Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) - -STATUS PATH + +STATUS PATH ────────────────────────────────────────────────────────────────────── -MODIFIED file_new [owner] -MODIFIED file_schema.json [owner] -diff result: 2c2 -< "genesis_datacard": { ---- -> 2"genesis_datacard": { -26c26 -< } -\ No newline at end of file ---- -> } -MODIFIED schema2.json [content, size] - +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + Summary: +0 added -0 deleted ~3 modified =4 unchanged ``` @@ -278,7 +278,7 @@ dsi.version("diff") | root_folder | TEXT | Repository root path | | commit_hash | TEXT | UUID4 hex (32 chars) | | committed_at | TEXT | ISO-8601 timestamp | -| owner_name | TEXT | Linux user who committed | +| owner_name | TEXT | Username of the committer | | message | TEXT | Optional commit message | | snapshot_path | TEXT | Path to rsync snapshot copy | | file_count | INTEGER | Number of files in commit | diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index c5e849fa..c7bed45c 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -14,7 +14,7 @@ root_folder TEXT NOT NULL, commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex committed_at TEXT NOT NULL, -- ISO-8601 timestamp - owner_name TEXT NOT NULL, -- Linux user who ran the commit + owner_name TEXT NOT NULL, -- Username of the committer message TEXT, snapshot_path TEXT NOT NULL, -- path to rsync copy file_count INTEGER NOT NULL, From 665bd14a47671389cf0caf29fdcd32e6e8454b81 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 21 May 2026 10:45:55 -0600 Subject: [PATCH 034/109] VCS: remove redundant table columns --- dsi/utils/version_control/README.md | 68 +++++++++---------- dsi/utils/version_control/dsi_vcs.py | 7 +- dsi/utils/version_control/tests/test_vcs.py | 29 +------- dsi/utils/version_control/vcs_db.py | 8 +-- .../version_control/vcs_metadata_helper.py | 9 +-- 5 files changed, 40 insertions(+), 81 deletions(-) diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index aa5ec8eb..23e6c85f 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -89,22 +89,22 @@ Example output: ```shell Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) - -STATUS PATH + +STATUS PATH ────────────────────────────────────────────────────────────────────── -MODIFIED file_new [owner] -MODIFIED file_schema.json [owner] -diff result: 2c2 -< "genesis_datacard": { ---- -> 2"genesis_datacard": { -26c26 -< } -\ No newline at end of file ---- -> } -MODIFIED schema2.json [content, size] - +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + Summary: +0 added -0 deleted ~3 modified =4 unchanged ``` @@ -288,26 +288,24 @@ dsi.version("diff") Stores metadata for each file in each commit. -| Column | Type | Description | -| ---------------------- | ---------- | -------------------------------- | -| id | INTEGER PK | Auto-increment | -| version_id | INTEGER FK | References versions(id) | -| root_folder | TEXT | Partition key | -| relative_path | TEXT | Path relative to root | -| absolute_path | TEXT | Full path | -| file_name | TEXT | Filename only | -| file_type | TEXT | file/dir/symlink/etc | -| md5_hash | TEXT | Content hash (files only) | -| lstat | TEXT | JSON of os.lstat() result | -| permissions_octal | TEXT | e.g., "0o755" | -| permissions_str | TEXT | e.g., "rwxr-xr-x" | -| owner_name | TEXT | Username | -| group_name | TEXT | Group name | -| setuid, setgid, sticky | INTEGER | Special bits (0/1) | -| acl_text | TEXT | Raw getfacl output | -| xattrs | TEXT | JSON dict of extended attributes | -| security_context | TEXT | SELinux context | -| symlink_target | TEXT | Target of symlink | +| Column | Type | Description | +| ---------------- | ---------- | -------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| absolute_path | TEXT | Full path | +| file_name | TEXT | Filename only | +| file_type | TEXT | file/dir/symlink/etc | +| md5_hash | TEXT | Content hash (files only) | +| lstat | TEXT | JSON of os.lstat() result | +| permissions_int | INTEGER | e.g. 755 | +| owner_name | TEXT | Username | +| group_name | TEXT | Group name | +| acl_text | TEXT | Raw getfacl output | +| xattrs | TEXT | JSON dict of extended attributes | +| security_context | TEXT | SELinux context | +| symlink_target | TEXT | Target of symlink | ### `staging` Table diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 67ea3965..15eede3e 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -351,8 +351,7 @@ def cmd_commit(self, message: str = ""): "version_id", "root_folder", "relative_path", "absolute_path", "file_name", "file_type", "md5_hash", "lstat", - "permissions_octal", "permissions_str", "owner_name", "group_name", - "setuid", "setgid", "sticky", + "permissions_int", "owner_name", "group_name", "acl_text", "xattrs", "security_context", "symlink_target", ] placeholders = ",".join("?" * len(cols)) @@ -439,7 +438,7 @@ def get_files(chash): conn.close() sys.exit(f"Commit '{chash}' not found.") rows = conn.execute( - "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_octal, " + "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_int, " " owner_name, group_name, lstat " "FROM file_entries WHERE version_id=?", (vid["id"],) ).fetchall() @@ -495,7 +494,7 @@ def get_files(chash): changes.append("content") result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) print(f"diff result: {result.stdout.strip()}") - if f1["permissions_octal"] != f2["permissions_octal"]: + if f1["permissions_int"] != f2["permissions_int"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: changes.append("owner") diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 6b7ba913..6a13c930 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -29,7 +29,7 @@ def latest_entries(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row return conn.execute( - "SELECT relative_path, file_type, permissions_octal, permissions_str, setgid " + "SELECT relative_path, file_type, permissions_int " "FROM file_entries WHERE version_id=(SELECT MAX(id) from versions) " "ORDER BY relative_path").fetchall() @@ -66,29 +66,4 @@ def test_add(tmp_path): rows = {row["relative_path"]: row for row in latest_entries(tmp_path)} empty_entry = rows["empty"] assert empty_entry["file_type"] == "dir" - assert empty_entry["permissions_str"] == "rwxrwsr-x" - assert eval(empty_entry["permissions_octal"]) == 0o2775 - assert empty_entry["setgid"] == 1 - - -def test_versioning(): - test = DSI() - # test.version("init", os.getcwd()) - # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - wpath = os.getcwd() - test.version("init", wpath) - assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") - - test.version("add", os.path.join(wpath, "a_dummy_file")) - print(">Single file added.") - test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) - print(">Multi file added.") - print(">Versioning initialized.") - - test.version("remove", os.path.join(wpath, "schema2.json")) - print(">Single file removed.") - - test.version("commit", "Tester Commits") - test.version("log") - test.version("diff") - assert True + assert empty_entry["permissions_int"] == 0o2775 diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index c7bed45c..989ce0da 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -42,16 +42,10 @@ lstat TEXT NOT NULL, -- ── Human-readable permission strings ───────────────────────────────── - permissions_octal TEXT, -- e.g. "0o755" - permissions_str TEXT, -- e.g. "rwxr-xr-x" + permissions_int INTEGER, -- e.g. "755" as an integer owner_name TEXT, group_name TEXT, - -- ── Special bits ────────────────────────────────────────────────────── - setuid INTEGER DEFAULT 0, - setgid INTEGER DEFAULT 0, - sticky INTEGER DEFAULT 0, - -- ── ACL (POSIX) ─────────────────────────────────────────────────────── acl_text TEXT, -- raw getfacl output diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index 8bc4da7c..6bf4a417 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -39,7 +39,6 @@ def permission_str(mode: int) -> str: (stat.S_IROTH, "r"), (stat.S_IWOTH, "w"), (stat.S_IXOTH, "x"), ): chars.append(char if mode & bit else "-") - # Replace x with s/t for setuid/setgid/sticky if mode & stat.S_ISUID: chars[2] = "s" if chars[2] == "x" else "S" if mode & stat.S_ISGID: @@ -219,16 +218,10 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: "lstat": json.dumps(lstat_dict), # human-readable permission strings - "permissions_octal": oct(stat.S_IMODE(mode)), - "permissions_str": permission_str(mode), + "permissions_int": stat.S_IMODE(mode), "owner_name": owner_name(s.st_uid), "group_name": group_name(s.st_gid), - # special-bit flags - "setuid": int(bool(mode & stat.S_ISUID)), - "setgid": int(bool(mode & stat.S_ISGID)), - "sticky": int(bool(mode & stat.S_ISVTX)), - # external / subprocess-derived attributes "acl_text": get_acl(abs_path), # "xattrs": get_xattrs(abs_path), From 5cb4d0dbd9b16ed0370aa99e60976befb4bc4417 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Wed, 8 Jul 2026 07:08:58 -0600 Subject: [PATCH 035/109] VCS: Use Merkle tree for commits. This augments the VCS database with a Merkle tree for content tracking. --- dsi/utils/version_control/README.md | 24 ++- dsi/utils/version_control/dsi_vcs.py | 221 ++++++++++++++++---- dsi/utils/version_control/merkle.py | 162 ++++++++++++++ dsi/utils/version_control/tests/test_vcs.py | 102 ++++++++- dsi/utils/version_control/vcs_db.py | 26 ++- 5 files changed, 496 insertions(+), 39 deletions(-) create mode 100644 dsi/utils/version_control/merkle.py diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index 23e6c85f..1c35401e 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -6,6 +6,7 @@ - **Full metadata capture**: permissions, ownership, ACLs, extended attributes, and SELinux contexts - **Rsync-based snapshots**: efficient storage with hard-link deduplication +- **Merkle commit chain**: SHA-256 commit IDs with per-path Merkle nodes for pruning unchanged subtrees - **SQLite database**: structured metadata storage for querying and diffing - **Complete file history**: MD5 hashes, file stats, and all metadata changes tracked @@ -276,7 +277,10 @@ dsi.version("diff") | ------------- | ---------- | --------------------------- | | id | INTEGER PK | Auto-increment ID | | root_folder | TEXT | Repository root path | -| commit_hash | TEXT | UUID4 hex (32 chars) | +| commit_hash | TEXT | SHA-256 Merkle commit hash | +| root_tree_hash | TEXT | SHA-256 hash of root tree | +| parent_commit_hash | TEXT | Previous commit hash | +| hash_algorithm | TEXT | Merkle format identifier | | committed_at | TEXT | ISO-8601 timestamp | | owner_name | TEXT | Username of the committer | | message | TEXT | Optional commit message | @@ -307,6 +311,24 @@ Stores metadata for each file in each commit. | security_context | TEXT | SELinux context | | symlink_target | TEXT | Target of symlink | +### `merkle_nodes` Table + +Stores the content-addressed tree node for each committed path, including the synthetic root path `.`. + +| Column | Type | Description | +| ------------------- | ---------- | --------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| file_type | TEXT | file/dir/symlink/etc | +| node_hash | TEXT | SHA-256 hash for this path node | +| metadata_hash | TEXT | SHA-256 hash of stable metadata | +| content_hash_sha256 | TEXT | SHA-256 file content hash | +| subtree_file_count | INTEGER | File count below this node | +| subtree_total_bytes | INTEGER | File bytes below this node | +| child_count | INTEGER | Direct child count for this node | + ### `staging` Table Temporary storage for files to be committed. diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 15eede3e..fbcd672f 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -22,15 +22,16 @@ import os import sys -import uuid import subprocess import json import datetime import shutil +import tempfile from typing import Optional from .vcs_db import DB_NAME, SNAPSHOTS_DIR, open_db from .vcs_metadata_helper import collect_metadata, collect_tree_metadata, owner_name +from .merkle import HASH_ALGORITHM, build_merkle_tree, commit_hash as merkle_commit_hash, parent_path # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -288,16 +289,16 @@ def cmd_commit(self, message: str = ""): # ── Previous snapshot for hard-link deduplication ──────────────────────── prev_row = cur.execute( - "SELECT snapshot_path FROM versions " + "SELECT commit_hash, snapshot_path FROM versions " "WHERE root_folder=? ORDER BY id DESC LIMIT 1", (self.root_folder,) ).fetchone() prev_snapshot = prev_row["snapshot_path"] if prev_row else None + parent_commit_hash = prev_row["commit_hash"] if prev_row else None committed_at = datetime.datetime.now(datetime.timezone.utc).isoformat() - commit_hash = uuid.uuid4().hex running_user = owner_name(os.getuid()) - snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) + snapshots_root = os.path.join(self.root_folder, SNAPSHOTS_DIR) # ── Validate staged paths before creating the snapshot ───────────────── print(f"Validating {len(staged_rows)} staged path(s)…") @@ -314,35 +315,54 @@ def cmd_commit(self, message: str = ""): sys.exit("No readable staged paths — commit aborted.") # ── Create a complete snapshot of the current repository tree ─────────── - print(f" Creating rsync snapshot → {snapshot_path}") + tmp_snapshot_path = tempfile.mkdtemp(prefix=".tmp-", dir=snapshots_root) + print(f" Creating rsync snapshot → {tmp_snapshot_path}") - ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot) + ok = rsync_snapshot(self.root_folder, tmp_snapshot_path, prev_snapshot) if not ok: conn.close() + shutil.rmtree(tmp_snapshot_path, ignore_errors=True) sys.exit("rsync failed — commit aborted.") try: - apply_snapshot_deletes(snapshot_path, self.root_folder, staged_deletes) + apply_snapshot_deletes(tmp_snapshot_path, self.root_folder, staged_deletes) except ValueError as e: conn.close() - shutil.rmtree(snapshot_path, ignore_errors=True) + shutil.rmtree(tmp_snapshot_path, ignore_errors=True) sys.exit(str(e)) # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_tree_metadata(snapshot_path, self.root_folder) + entries = collect_tree_metadata(tmp_snapshot_path, self.root_folder) total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") file_count = sum(1 for e in entries if e.get("file_type") == "file") print(f" {file_count} file(s), {total_bytes:,} bytes") + root_tree_hash, merkle_nodes = build_merkle_tree(entries, tmp_snapshot_path) + commit_hash = merkle_commit_hash( + root_tree_hash=root_tree_hash, + parent_commit_hash=parent_commit_hash, + committed_at=committed_at, + owner_name=running_user, + message=message, + file_count=file_count, + total_bytes=total_bytes, + ) + snapshot_path = os.path.join(snapshots_root, commit_hash[:12]) + if os.path.exists(snapshot_path): + conn.close() + shutil.rmtree(tmp_snapshot_path, ignore_errors=True) + sys.exit(f"Snapshot path already exists for commit prefix: {snapshot_path}") + os.rename(tmp_snapshot_path, snapshot_path) + # ── Insert version row ─────────────────────────────────────────────────── cur.execute( """INSERT INTO versions - (root_folder, commit_hash, committed_at, owner_name, message, - snapshot_path, file_count, total_bytes) - VALUES (?,?,?,?,?,?,?,?)""", - (self.root_folder, commit_hash, committed_at, running_user, message, - snapshot_path, file_count, total_bytes) + (root_folder, commit_hash, root_tree_hash, parent_commit_hash, hash_algorithm, + committed_at, owner_name, message, snapshot_path, file_count, total_bytes) + VALUES (?,?,?,?,?,?,?,?,?,?,?)""", + (self.root_folder, commit_hash, root_tree_hash, parent_commit_hash, HASH_ALGORITHM, + committed_at, running_user, message, snapshot_path, file_count, total_bytes) ) version_id = cur.lastrowid @@ -370,6 +390,27 @@ def cmd_commit(self, message: str = ""): ] ) + merkle_cols = [ + "version_id", "root_folder", "relative_path", "file_type", + "node_hash", "metadata_hash", "content_hash_sha256", + "subtree_file_count", "subtree_total_bytes", "child_count", + ] + merkle_placeholders = ",".join("?" * len(merkle_cols)) + merkle_col_str = ",".join(merkle_cols) + + cur.executemany( + f"INSERT INTO merkle_nodes ({merkle_col_str}) VALUES ({merkle_placeholders})", + [ + tuple( + version_id if c == "version_id" else + self.root_folder if c == "root_folder" else + node.get(c) + for c in merkle_cols + ) + for node in merkle_nodes + ] + ) + # ── Clear staging after a successful commit ────────────────────────────── cur.execute("DELETE FROM staging WHERE root_folder=?", (self.root_folder,)) @@ -397,11 +438,11 @@ def cmd_log(self): print("No versions yet. Run 'commit' first.") return - print(f"{'COMMIT HASH':<34} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") - print("─" * 100) + print(f"{'COMMIT HASH':<66} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") + print("─" * 132) for r in rows: msg = (r["message"] or "")[:35] - print(f"{r['commit_hash']:<34} {r['owner_name']:<16} {r['committed_at']:<28}" + print(f"{r['commit_hash']:<66} {r['owner_name']:<16} {r['committed_at']:<28}" f"{r['file_count']:>7} {r['total_bytes']:>15,} {msg}") @@ -413,6 +454,12 @@ def get_files_in_root_folder(): files = {} for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): dirnames[:] = [d for d in dirnames if d not in {DB_NAME, SNAPSHOTS_DIR}] + if dirpath != root_folder: + abs_path = dirpath + rel_path = os.path.relpath(abs_path, root_folder) + files[rel_path] = collect_metadata(abs_path, self.root_folder) + files[rel_path]["absolute_path"] = abs_path + files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} for fname in filenames: if fname not in {DB_NAME, SNAPSHOTS_DIR}: abs_path = os.path.join(dirpath, fname) @@ -421,38 +468,132 @@ def get_files_in_root_folder(): files[rel_path]["absolute_path"] = abs_path files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} return files - - def get_files(chash): - vid = None + + def get_version(chash): if chash == "latest": - vid = conn.execute( - "SELECT id, commit_hash FROM versions WHERE root_folder=? ORDER BY id DESC LIMIT 1", + row = conn.execute( + "SELECT id, commit_hash, snapshot_path, root_tree_hash FROM versions " + "WHERE root_folder=? ORDER BY id DESC LIMIT 1", (root_folder,) ).fetchone() else: - vid = conn.execute( - "SELECT id, commit_hash FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + row = conn.execute( + "SELECT id, commit_hash, snapshot_path, root_tree_hash FROM versions " + "WHERE root_folder = ? AND commit_hash LIKE ?", (root_folder, chash + "%") ).fetchone() - if not vid: + if not row: conn.close() sys.exit(f"Commit '{chash}' not found.") - rows = conn.execute( + return row + + def get_files(chash): + vid = get_version(chash) + return get_files_for_version(vid) + + def get_files_for_version(version, only_paths: Optional[set[str]] = None): + path_filter = None if only_paths is None else sorted(only_paths) + if path_filter == []: + return {} + + rows = [] + base_sql = ( "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_int, " " owner_name, group_name, lstat " - "FROM file_entries WHERE version_id=?", (vid["id"],) - ).fetchall() + "FROM file_entries WHERE version_id=?" + ) + if path_filter is None: + rows = conn.execute(base_sql, (version["id"],)).fetchall() + else: + for i in range(0, len(path_filter), 500): + chunk = path_filter[i:i + 500] + placeholders = ",".join("?" * len(chunk)) + rows.extend( + conn.execute( + f"{base_sql} AND relative_path IN ({placeholders})", + (version["id"], *chunk), + ).fetchall() + ) + + snapshot_path = version["snapshot_path"] result = {} for r in rows: rec = dict(r) # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. - snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, vid["commit_hash"][:12]) rec["absolute_path"] = snapshot_target(snapshot_path, r["relative_path"]) rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} result[r["relative_path"]] = rec return result + def get_merkle_nodes(version_id): + rows = conn.execute( + "SELECT relative_path, file_type, node_hash, metadata_hash " + "FROM merkle_nodes WHERE version_id=?", + (version_id,), + ).fetchall() + return {r["relative_path"]: dict(r) for r in rows} + + def children_index(nodes): + children = {path: [] for path in nodes} + for path in nodes: + if path == ".": + continue + children.setdefault(parent_path(path), []).append(path) + return children + + def add_subtree(changed_paths, path, children): + if path != ".": + changed_paths.add(path) + for child_path in children.get(path, []): + add_subtree(changed_paths, child_path, children) + + def changed_paths_from_merkle(v1, v2): + nodes1 = get_merkle_nodes(v1["id"]) + nodes2 = get_merkle_nodes(v2["id"]) + children1 = children_index(nodes1) + children2 = children_index(nodes2) + changed_paths = set() + + def walk(path): + n1 = nodes1.get(path) + n2 = nodes2.get(path) + if n1 and n2 and n1["node_hash"] == n2["node_hash"]: + return + if n1 is None: + add_subtree(changed_paths, path, children2) + return + if n2 is None: + add_subtree(changed_paths, path, children1) + return + + if path != ".": + if n1["file_type"] != n2["file_type"] or n1["metadata_hash"] != n2["metadata_hash"]: + changed_paths.add(path) + elif n1["file_type"] != "dir": + changed_paths.add(path) + + if n1["file_type"] == "dir" or n2["file_type"] == "dir": + child_paths = set(children1.get(path, [])) | set(children2.get(path, [])) + for child_path in sorted(child_paths): + walk(child_path) + + walk(".") + return changed_paths + + def unchanged_count_from_merkle(v1, v2): + row = conn.execute( + "SELECT COUNT(*) AS count " + "FROM merkle_nodes a " + "JOIN merkle_nodes b " + " ON a.relative_path = b.relative_path " + " AND a.node_hash = b.node_hash " + "WHERE a.version_id=? AND b.version_id=? AND a.relative_path <> '.'", + (v1["id"], v2["id"]), + ).fetchone() + return row["count"] if row else 0 + files1 = files2 = {} + unchanged = 0 if c1 is None and c2 is None: files1 = get_files_in_root_folder() files2 = get_files("latest") @@ -463,12 +604,16 @@ def get_files(chash): files1 = get_files(c1) files2 = get_files_in_root_folder() else: - files1 = get_files(c1) - files2 = get_files(c2) + version1 = get_version(c1) + version2 = get_version(c2) + changed_paths = changed_paths_from_merkle(version1, version2) + unchanged = unchanged_count_from_merkle(version1, version2) + files1 = get_files_for_version(version1, changed_paths) + files2 = get_files_for_version(version2, changed_paths) conn.close() all_paths = sorted(set(files1) | set(files2)) - added = deleted = modified = unchanged = 0 + added = deleted = modified = 0 print(f"Diff {c1} → {c2} ({root_folder})\n") print(f"{'STATUS':<10} {'PATH'}") @@ -490,10 +635,14 @@ def get_files(chash): else: f1, f2 = files1[p], files2[p] changes = [] - if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: - changes.append("content") - result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) - print(f"diff result: {result.stdout.strip()}") + if f1["file_type"] != f2["file_type"]: + changes.append("type") + if f1["md5_hash"] != f2["md5_hash"]: + if f1["md5_hash"] or f2["md5_hash"]: + changes.append("content") + if f1["md5_hash"] and f2["md5_hash"]: + result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) + print(f"diff result: {result.stdout.strip()}") if f1["permissions_int"] != f2["permissions_int"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: diff --git a/dsi/utils/version_control/merkle.py b/dsi/utils/version_control/merkle.py new file mode 100644 index 00000000..3ea1c9e5 --- /dev/null +++ b/dsi/utils/version_control/merkle.py @@ -0,0 +1,162 @@ +import hashlib +import json +import os +from typing import Any, Optional + + +HASH_ALGORITHM = "sha256-merkle-v1" + + +def _canonical_json(data: dict[str, Any]) -> bytes: + return json.dumps(data, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def hash_payload(data: dict[str, Any]) -> str: + return hashlib.sha256(_canonical_json(data)).hexdigest() + + +def sha256_file(path: str, chunk_size: int = 1 << 20) -> Optional[str]: + h = hashlib.sha256() + try: + with open(path, "rb") as f: + while chunk := f.read(chunk_size): + h.update(chunk) + return h.hexdigest() + except (PermissionError, OSError): + return None + + +def parent_path(relative_path: str) -> str: + parent = os.path.dirname(relative_path) + return parent if parent else "." + + +def _stable_metadata(entry: dict[str, Any], relative_path: str) -> dict[str, Any]: + if relative_path == ".": + return { + "relative_path": ".", + "file_type": "dir", + "root": True, + } + + return { + "relative_path": relative_path, + "file_name": os.path.basename(relative_path), + "file_type": entry.get("file_type"), + "permissions_int": entry.get("permissions_int"), + "owner_name": entry.get("owner_name"), + "group_name": entry.get("group_name"), + "acl_text": entry.get("acl_text"), + "xattrs": entry.get("xattrs"), + "security_context": entry.get("security_context"), + "symlink_target": entry.get("symlink_target"), + } + + +def build_merkle_tree(entries: list[dict[str, Any]], snapshot_path: str) -> tuple[str, list[dict[str, Any]]]: + entries_by_path = {entry["relative_path"]: entry for entry in entries} + children_by_parent: dict[str, list[tuple[str, str]]] = {".": []} + + for relative_path in entries_by_path: + parent = parent_path(relative_path) + children_by_parent.setdefault(parent, []).append((os.path.basename(relative_path), relative_path)) + children_by_parent.setdefault(relative_path, []) + + nodes: dict[str, dict[str, Any]] = {} + + def build_node(relative_path: str) -> dict[str, Any]: + if relative_path in nodes: + return nodes[relative_path] + + entry = entries_by_path.get(relative_path, {}) + file_type = "dir" if relative_path == "." else entry.get("file_type") + metadata_hash = hash_payload( + { + "object": "dsi-vcs-metadata-v1", + "metadata": _stable_metadata(entry, relative_path), + } + ) + child_rows = sorted(children_by_parent.get(relative_path, [])) + content_hash = None + subtree_file_count = 0 + subtree_total_bytes = 0 + + if file_type == "dir": + child_payload = [] + for name, child_path in child_rows: + child = build_node(child_path) + subtree_file_count += child["subtree_file_count"] + subtree_total_bytes += child["subtree_total_bytes"] + child_payload.append( + { + "name": name, + "file_type": child["file_type"], + "node_hash": child["node_hash"], + } + ) + node_hash = hash_payload( + { + "object": "dsi-vcs-tree-v1", + "metadata_hash": metadata_hash, + "children": child_payload, + } + ) + elif file_type == "file": + content_hash = sha256_file(os.path.join(snapshot_path, relative_path)) + subtree_file_count = 1 + subtree_total_bytes = entry.get("_st_size") or 0 + node_hash = hash_payload( + { + "object": "dsi-vcs-file-v1", + "metadata_hash": metadata_hash, + "content_hash_sha256": content_hash, + } + ) + else: + node_hash = hash_payload( + { + "object": "dsi-vcs-special-v1", + "file_type": file_type, + "metadata_hash": metadata_hash, + } + ) + + node = { + "relative_path": relative_path, + "file_type": file_type, + "node_hash": node_hash, + "metadata_hash": metadata_hash, + "content_hash_sha256": content_hash, + "subtree_file_count": subtree_file_count, + "subtree_total_bytes": subtree_total_bytes, + "child_count": len(child_rows), + } + nodes[relative_path] = node + return node + + root = build_node(".") + return root["node_hash"], [nodes[path] for path in sorted(nodes)] + + +def commit_hash( + root_tree_hash: str, + parent_commit_hash: Optional[str], + committed_at: str, + owner_name: str, + message: str, + file_count: int, + total_bytes: int, +) -> str: + return hash_payload( + { + "object": "dsi-vcs-commit-v1", + "hash_algorithm": HASH_ALGORITHM, + "root_tree_hash": root_tree_hash, + "parent_commit_hash": parent_commit_hash, + "committed_at": committed_at, + "owner_name": owner_name, + "message": message or "", + "file_count": file_count, + "total_bytes": total_bytes, + } + ) diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 6a13c930..d34009f2 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -1,4 +1,5 @@ import os +import re import sqlite3 from pathlib import Path from shutil import which @@ -22,9 +23,23 @@ def commits(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row return conn.execute( - "SELECT id, commit_hash, snapshot_path FROM versions ORDER BY id" + "SELECT id, commit_hash, root_tree_hash, parent_commit_hash, " + "hash_algorithm, snapshot_path FROM versions ORDER BY id" ).fetchall() +def merkle_nodes(repo_path, version_id): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return { + row["relative_path"]: row + for row in conn.execute( + "SELECT relative_path, file_type, node_hash, metadata_hash, " + "content_hash_sha256, subtree_file_count, subtree_total_bytes, child_count " + "FROM merkle_nodes WHERE version_id=? ORDER BY relative_path", + (version_id,), + ).fetchall() + } + def latest_entries(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row @@ -55,6 +70,13 @@ def test_add(tmp_path): repo.cmd_commit("beta") alpha, beta = commits(tmp_path) + assert re.fullmatch(r"[0-9a-f]{64}", alpha["commit_hash"]) + assert re.fullmatch(r"[0-9a-f]{64}", beta["commit_hash"]) + assert alpha["hash_algorithm"] == "sha256-merkle-v1" + assert beta["parent_commit_hash"] == alpha["commit_hash"] + assert Path(alpha["snapshot_path"]).name == alpha["commit_hash"][:12] + assert Path(beta["snapshot_path"]).name == beta["commit_hash"][:12] + alpha_path = Path(alpha["snapshot_path"]) beta_path = Path(beta["snapshot_path"]) assert (alpha_path / "a.txt").read_text() == "foo" @@ -67,3 +89,81 @@ def test_add(tmp_path): empty_entry = rows["empty"] assert empty_entry["file_type"] == "dir" assert empty_entry["permissions_int"] == 0o2775 + + beta_nodes = merkle_nodes(tmp_path, beta["id"]) + assert "." in beta_nodes + assert beta_nodes["."]["node_hash"] == beta["root_tree_hash"] + assert beta_nodes["a.txt"]["file_type"] == "file" + assert re.fullmatch(r"[0-9a-f]{64}", beta_nodes["a.txt"]["content_hash_sha256"]) + + +def test_merkle_tracks_content_metadata_symlink_and_deletes(tmp_path): + require_rsync() + repo = Version(str(tmp_path)) + + nested = tmp_path / "nested" + nested.mkdir() + file_path = nested / "data.txt" + file_path.write_text("one") + link_path = tmp_path / "link.txt" + link_path.symlink_to("nested/data.txt") + + repo.cmd_add(["nested", "link.txt"]) + repo.cmd_commit("initial") + first = commits(tmp_path)[0] + first_nodes = merkle_nodes(tmp_path, first["id"]) + + file_path.write_text("two more") + repo.cmd_add(["nested/data.txt"]) + repo.cmd_commit("content") + second = commits(tmp_path)[1] + second_nodes = merkle_nodes(tmp_path, second["id"]) + assert second_nodes["nested/data.txt"]["content_hash_sha256"] != first_nodes["nested/data.txt"]["content_hash_sha256"] + assert second_nodes["nested/data.txt"]["metadata_hash"] == first_nodes["nested/data.txt"]["metadata_hash"] + assert second["root_tree_hash"] != first["root_tree_hash"] + + file_path.chmod(0o640) + repo.cmd_add(["nested/data.txt"]) + repo.cmd_commit("metadata") + third = commits(tmp_path)[2] + third_nodes = merkle_nodes(tmp_path, third["id"]) + assert third_nodes["nested/data.txt"]["content_hash_sha256"] == second_nodes["nested/data.txt"]["content_hash_sha256"] + assert third_nodes["nested/data.txt"]["metadata_hash"] != second_nodes["nested/data.txt"]["metadata_hash"] + + link_path.unlink() + link_path.symlink_to("missing.txt") + repo.cmd_add(["link.txt"]) + repo.cmd_commit("symlink") + fourth = commits(tmp_path)[3] + fourth_nodes = merkle_nodes(tmp_path, fourth["id"]) + assert fourth_nodes["link.txt"]["metadata_hash"] != third_nodes["link.txt"]["metadata_hash"] + + repo.cmd_delete(["nested/data.txt"]) + repo.cmd_commit("delete") + fifth = commits(tmp_path)[4] + fifth_nodes = merkle_nodes(tmp_path, fifth["id"]) + assert "nested/data.txt" not in fifth_nodes + assert fifth["parent_commit_hash"] == fourth["commit_hash"] + + +def test_diff_short_circuits_equal_root_trees(tmp_path, capsys): + require_rsync() + repo = Version(str(tmp_path)) + + (tmp_path / "a.txt").write_text("same") + repo.cmd_add(["a.txt"]) + repo.cmd_commit("first") + + repo.cmd_delete(["does-not-exist.txt"]) + repo.cmd_commit("same tree") + + first, second = commits(tmp_path) + assert first["root_tree_hash"] == second["root_tree_hash"] + assert first["commit_hash"] != second["commit_hash"] + + repo.cmd_diff(first["commit_hash"][:12], second["commit_hash"][:12]) + out = capsys.readouterr().out + assert "ADDED" not in out + assert "DELETED" not in out + assert "MODIFIED" not in out + assert "Summary: +0 added -0 deleted ~0 modified" in out diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index 989ce0da..2724cd7a 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -12,7 +12,10 @@ CREATE TABLE IF NOT EXISTS versions ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, - commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex + commit_hash TEXT NOT NULL, -- SHA-256 Merkle commit hash + root_tree_hash TEXT NOT NULL, + parent_commit_hash TEXT, + hash_algorithm TEXT NOT NULL, committed_at TEXT NOT NULL, -- ISO-8601 timestamp owner_name TEXT NOT NULL, -- Username of the committer message TEXT, @@ -65,6 +68,27 @@ CREATE INDEX IF NOT EXISTS idx_file_entries_path ON file_entries(root_folder, relative_path); +CREATE TABLE IF NOT EXISTS merkle_nodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version_id INTEGER NOT NULL REFERENCES versions(id) ON DELETE CASCADE, + root_folder TEXT NOT NULL, + relative_path TEXT NOT NULL, + file_type TEXT NOT NULL, + node_hash TEXT NOT NULL, + metadata_hash TEXT NOT NULL, + content_hash_sha256 TEXT, + subtree_file_count INTEGER NOT NULL, + subtree_total_bytes INTEGER NOT NULL, + child_count INTEGER NOT NULL, + UNIQUE(version_id, relative_path) +); + +CREATE INDEX IF NOT EXISTS idx_merkle_nodes_root_path + ON merkle_nodes(root_folder, version_id, relative_path); + +CREATE INDEX IF NOT EXISTS idx_merkle_nodes_hash + ON merkle_nodes(root_folder, node_hash); + CREATE TABLE IF NOT EXISTS staging ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, From 1b39034219455d879d980c3de475434126d9a38d Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 26 Mar 2026 09:48:29 -0600 Subject: [PATCH 036/109] Version control first demo --- tools/version_control/dsi_vcs.py | 516 +++++++++++++++++++ tools/version_control/requirements.txt | 1 + tools/version_control/vcs_db.py | 94 ++++ tools/version_control/vcs_metadata_helper.py | 248 +++++++++ 4 files changed, 859 insertions(+) create mode 100755 tools/version_control/dsi_vcs.py create mode 100644 tools/version_control/requirements.txt create mode 100644 tools/version_control/vcs_db.py create mode 100644 tools/version_control/vcs_metadata_helper.py diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py new file mode 100755 index 00000000..44ebaa56 --- /dev/null +++ b/tools/version_control/dsi_vcs.py @@ -0,0 +1,516 @@ +#!/usr/bin/env python3 +""" +dsi_vcs.py — rsync-based file version control system +Captures full Linux file metadata (stat, ACL, xattrs), MD5 hash, +and stores versioned snapshots in SQLite. + +Usage: + python dsi_vcs.py init # init repo + python dsi_vcs.py commit [message] # commit a new version + python dsi_vcs.py log # list versions + python dsi_vcs.py diff # diff two versions + python dsi_vcs.py restore # restore a version + +Requirements: + pip install pyxattr # for extended attributes + sudo apt install acl # for getfacl (ACL support) + rsync must be installed # for snapshot copies +""" + +import os +import sys +import uuid +import subprocess +import json +import datetime +import argparse +from pathlib import Path +from typing import Optional + +from vcs_db import * +from vcs_metadata_helper import * + +# ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── + +def rsync_snapshot( + root_folder: str, + dest_path: str, + prev_snapshot: Optional[str] = None, + rel_paths: Optional[list[str]] = None, +) -> bool: + """ + Copy files from root_folder → dest_path using rsync hard-link deduplication. + If rel_paths is given, only those paths are synced (via --files-from). + If prev_snapshot is provided, unchanged files are hard-linked (saves disk). + """ + import tempfile + + os.makedirs(dest_path, exist_ok=True) + cmd = ["rsync", "-aAXH"] # for linux + if sys.platform == "darwin": + cmd = ["rsync", "-aEH"] # for macOS + + if prev_snapshot and os.path.isdir(prev_snapshot): + cmd += ["--link-dest", os.path.abspath(prev_snapshot)] + + # Write a temp file-list when only staging specific paths + files_from_tmp = None + if rel_paths is not None: + tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) + tmp.write("\n".join(rel_paths)) + tmp.close() + files_from_tmp = tmp.name + cmd += ["--files-from", files_from_tmp] + else: + cmd += [ + "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + ] + + cmd += [ + root_folder.rstrip("/") + "/", + dest_path.rstrip("/") + "/", + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): # 24 = some files vanished (ok) + print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) + return False + if sys.platform == "darwin": + for rel_path in rel_paths: + set_acl(dest_path, get_acl(rel_path) or "") + return True + finally: + if files_from_tmp: + os.unlink(files_from_tmp) + + +# ─────────────────────────── COMMANDS ──────────────────────────────────────── +class DSIVCS(): + + def __init__(self, folder: str): + self.root_folder = os.path.abspath(folder) + if not os.path.isdir(self.root_folder): + sys.exit(f"Error: '{self.root_folder}' is not a directory.") + + conn = open_db(self.root_folder) + conn.close() + print(f"Initialized dsi-vcs repository in: {self.root_folder}") + print(f" Snapshots: {self.root_folder}/{SNAPSHOTS_DIR}/") + + + def cmd_add(self, paths: list[str]): + """ + Stage one or more files/directories for the next commit. + Directories are expanded recursively; each resolved file is inserted into + the staging table. Adding an already-staged path is a silent no-op. + paths should be a list of relative paths to root_folder. + """ + # print(f"Staging {len(paths)} path(s) for commit…") + # print(paths) + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + added_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + staged = 0 + skip_names = {DB_NAME, SNAPSHOTS_DIR} + + def stage_path(abs_path: str): + nonlocal staged + # Reject paths outside the root + try: + os.path.relpath(abs_path, self.root_folder) + except ValueError: + print(f" [skip] {abs_path}: outside root folder") + return + + if not os.path.lexists(abs_path): + print(f" [skip] {abs_path}: path does not exist") + return + + cur.execute( + "INSERT OR IGNORE INTO staging (root_folder, absolute_path, added_at) " + "VALUES (?, ?, ?)", + (self.root_folder, abs_path, added_at) + ) + if cur.rowcount: + staged += 1 + + for raw in paths: + abs_path = os.path.join(self.root_folder, raw) + + if os.path.isdir(abs_path): + # Expand directory recursively + for dirpath, dirnames, filenames in os.walk(abs_path, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in skip_names] + for fname in filenames: + if fname in skip_names: + continue + stage_path(os.path.join(dirpath, fname)) + else: + stage_path(abs_path) + + conn.commit() + conn.close() + print(f" {staged} file(s) added to staging.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' to stage files.") + return + + print(f"Staged files ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel}") + + def cmd_remove(self, paths: list[str]): + """Remove file(s) from the staging area without touching the actual files.""" + root_folder = os.path.abspath(self.root_folder) + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + removed = 0 + + for raw in paths: + abs_path = os.path.abspath(raw) + cur.execute( + "DELETE FROM staging WHERE root_folder=? AND absolute_path=?", + (root_folder, abs_path) + ) + if cur.rowcount: + rel = os.path.relpath(abs_path, root_folder) + print(f" Unstaged: {rel}") + removed += 1 + else: + rel = os.path.relpath(abs_path, root_folder) + print(f" [not staged] {rel}") + + conn.commit() + conn.close() + print(f" {removed} file(s) removed from staging.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' to stage files.") + return + + print(f"Staged files ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel}") + + def cmd_commit(self, message: str = ""): + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + + # ── Load staged paths ──────────────────────────────────────────────────── + staged_rows = cur.execute( + "SELECT absolute_path FROM staging WHERE root_folder = ? ORDER BY absolute_path", + (self.root_folder,) + ).fetchall() + + if not staged_rows: + conn.close() + sys.exit("Nothing staged. Use 'add' to stage files before committing.") + + staged_paths = [r["absolute_path"] for r in staged_rows] + + # ── Previous snapshot for hard-link deduplication ──────────────────────── + prev_row = cur.execute( + "SELECT snapshot_path FROM versions " + "WHERE root_folder=? ORDER BY id DESC LIMIT 1", + (self.root_folder,) + ).fetchone() + prev_snapshot = prev_row["snapshot_path"] if prev_row else None + + committed_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + commit_hash = uuid.uuid4().hex + running_user = owner_name(os.getuid()) + snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) + + # ── Collect metadata for each staged file ─────────────────────────────── + print(f"Collecting metadata for {len(staged_paths)} staged file(s)…") + entries = [] + for abs_path in staged_paths: + e = collect_metadata(abs_path, self.root_folder) + if "error" in e: + print(f" [skip] {e['relative_path']}: {e['error']}") + else: + entries.append(e) + + if not entries: + conn.close() + sys.exit("No readable staged files — commit aborted.") + + total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") + file_count = sum(1 for e in entries if e.get("file_type") == "file") + + # ── rsync only the staged relative paths ──────────────────────────────── + rel_paths = [e["relative_path"] for e in entries] + print(f" {file_count} file(s), {total_bytes:,} bytes") + print(f" Creating rsync snapshot → {snapshot_path}") + + ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot, rel_paths) + if not ok: + conn.close() + sys.exit("rsync failed — commit aborted.") + + # ── Insert version row ─────────────────────────────────────────────────── + cur.execute( + """INSERT INTO versions + (root_folder, commit_hash, committed_at, owner_name, message, + snapshot_path, file_count, total_bytes) + VALUES (?,?,?,?,?,?,?,?)""", + (self.root_folder, commit_hash, committed_at, running_user, message, + snapshot_path, file_count, total_bytes) + ) + version_id = cur.lastrowid + + # ── Bulk-insert file entries ───────────────────────────────────────────── + cols = [ + "version_id", "root_folder", "relative_path", "absolute_path", + "file_name", "file_type", "md5_hash", + "lstat", + "permissions_octal", "permissions_str", "owner_name", "group_name", + "setuid", "setgid", "sticky", + "acl_text", "xattrs", "security_context", "symlink_target", + ] + placeholders = ",".join("?" * len(cols)) + col_str = ",".join(cols) + + cur.executemany( + f"INSERT INTO file_entries ({col_str}) VALUES ({placeholders})", + [ + tuple( + version_id if c == "version_id" else + self.root_folder if c == "root_folder" else + e.get(c) + for c in cols + ) + for e in entries + ] + ) + + # ── Clear staging after a successful commit ────────────────────────────── + cur.execute("DELETE FROM staging WHERE root_folder=?", (self.root_folder,)) + + conn.commit() + conn.close() + + print(f"\n Committed {commit_hash} at {committed_at}") + print(f" Short hash : {commit_hash[:12]}") + print(f" Owner : {running_user}") + if message: + print(f" Message : {message}") + + + def cmd_log(self): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT commit_hash, committed_at, owner_name, message, file_count, total_bytes " + "FROM versions WHERE root_folder=? ORDER BY id", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("No versions yet. Run 'commit' first.") + return + + print(f"{'COMMIT HASH':<34} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") + print("─" * 100) + for r in rows: + msg = (r["message"] or "")[:35] + print(f"{r['commit_hash']:<34} {r['owner_name']:<16} {r['committed_at']:<28}" + f"{r['file_count']:>7} {r['total_bytes']:>15,} {msg}") + + + def cmd_diff(self, c1: str, c2: str): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + + def get_files(chash): + vid = conn.execute( + "SELECT id FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, chash + "%") + ).fetchone() + if not vid: + conn.close() + sys.exit(f"Commit '{chash}' not found.") + rows = conn.execute( + "SELECT relative_path, file_type, md5_hash, permissions_octal, " + " owner_name, group_name, lstat " + "FROM file_entries WHERE version_id=?", (vid["id"],) + ).fetchall() + result = {} + for r in rows: + rec = dict(r) + # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. + rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} + result[r["relative_path"]] = rec + return result + + files1 = get_files(c1) + files2 = get_files(c2) + conn.close() + + all_paths = sorted(set(files1) | set(files2)) + added = deleted = modified = unchanged = 0 + + print(f"Diff {c1} → {c2} ({root_folder})\n") + print(f"{'STATUS':<10} {'PATH'}") + print("─" * 70) + + for p in all_paths: + # if p in files1 and p not in files2: + # print(f"{'DELETED':<10} {p:<40} in {c2}") + # deleted += 1 + # elif p in files2 and p not in files1: + # print(f"{'ADDED':<10} {p:<40} in {c2}") + # added += 1 + if p not in files1: + print(f"{'ADDED':<10} {p:<40} in {c2}") + added += 1 + elif p not in files2: + print(f"{'DELETED':<10} {p:<40} in {c2}") + deleted += 1 + else: + f1, f2 = files1[p], files2[p] + changes = [] + if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: + changes.append("content") + if f1["permissions_octal"] != f2["permissions_octal"]: + changes.append("perms") + if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: + changes.append("owner") + if f1["lstat"].get("st_size") != f2["lstat"].get("st_size"): + changes.append("size") + if changes: + print(f"{'MODIFIED':<10} {p} [{', '.join(changes)}]") + modified += 1 + else: + unchanged += 1 + + print(f"\nSummary: +{added} added -{deleted} deleted ~{modified} modified ={unchanged} unchanged") + + + def cmd_restore(self, commit_hash: str): + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + row = conn.execute( + "SELECT commit_hash, snapshot_path FROM versions " + "WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, commit_hash + "%") + ).fetchone() + conn.close() + + if not row: + sys.exit(f"Commit '{commit_hash}' not found.") + + full_hash = row["commit_hash"] + snapshot = row["snapshot_path"] + if not os.path.isdir(snapshot): + sys.exit(f"Snapshot directory missing: {snapshot}") + + print(f"Restoring {commit_hash} from {snapshot} → {root_folder}") + cmd = [ + "rsync", "-aAXH", "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + snapshot.rstrip("/") + "/", + root_folder.rstrip("/") + "/", + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): + sys.exit(f"rsync restore failed:\n{result.stderr}") + print(f" Restored to {full_hash}") + + +# ─────────────────────────── CLI ───────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser( + description="dsi_vcs — rsync-based file version control with full Linux metadata" + ) + sub = parser.add_subparsers(dest="command", required=True) + + p_init = sub.add_parser("init", help="Initialize a new repo") + # p_init.add_argument("root_folder") + + p_add = sub.add_parser("add", help="add file or directory to staging") + p_add.add_argument('files', nargs='+', type=str) + + p_remove = sub.add_parser("remove", help="remove file or directory from staging") + p_remove.add_argument('files', nargs='+', type=str) + + p_commit = sub.add_parser("commit", help="Snapshot and commit staged files") + p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) + + p_log = sub.add_parser("log", help="List all commits") + + p_diff = sub.add_parser("diff", help="Diff two commits") + p_diff.add_argument("c1") + p_diff.add_argument("c2") + + p_restore = sub.add_parser("restore", help="Restore a version") + p_restore.add_argument("root_folder") + p_restore.add_argument("version") + + args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) + + # parser.print_help() + # parser.format_help() + if args.command == "init": + vcs = DSIVCS(os.getcwd()) + elif args.command == "add": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_add(args.files) + elif args.command == "remove": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_remove(args.files) + elif args.command == "commit": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_commit(args.message) + elif args.command == "diff": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_diff(args.c1, args.c2) + elif args.command == "log": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_log() + # elif args.command == "diff": cmd_diff(args.root_folder, args.v1, args.v2) + # elif args.command == "restore": cmd_restore(args.root_folder, args.version) + +if __name__ == "__main__": + # print("\n=== dsi-vcs: rsync-based file version control ===\n") + # print(os.getcwd()) + main() \ No newline at end of file diff --git a/tools/version_control/requirements.txt b/tools/version_control/requirements.txt new file mode 100644 index 00000000..11258685 --- /dev/null +++ b/tools/version_control/requirements.txt @@ -0,0 +1 @@ +pyxattr>=0.8.1 \ No newline at end of file diff --git a/tools/version_control/vcs_db.py b/tools/version_control/vcs_db.py new file mode 100644 index 00000000..72ef6990 --- /dev/null +++ b/tools/version_control/vcs_db.py @@ -0,0 +1,94 @@ +import sqlite3 +import os + +# ─────────────────────────── CONFIG ────────────────────────────────────────── + +DB_NAME = ".dsi_vcs.db" # SQLite DB stored inside the root folder +SNAPSHOTS_DIR = ".dsi_vcs_snapshots" # rsync snapshot copies live here + +# ─────────────────────────── DATABASE ──────────────────────────────────────── + +SCHEMA = """ +CREATE TABLE IF NOT EXISTS versions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + root_folder TEXT NOT NULL, + commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex + committed_at TEXT NOT NULL, -- ISO-8601 timestamp + owner_name TEXT NOT NULL, -- Linux user who ran the commit + message TEXT, + snapshot_path TEXT NOT NULL, -- path to rsync copy + file_count INTEGER NOT NULL, + total_bytes INTEGER NOT NULL, + UNIQUE(root_folder, commit_hash) +); + +CREATE TABLE IF NOT EXISTS file_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version_id INTEGER NOT NULL REFERENCES versions(id) ON DELETE CASCADE, + root_folder TEXT NOT NULL, -- partition key / lookup key + relative_path TEXT NOT NULL, -- path relative to root + absolute_path TEXT NOT NULL, + + -- ── Identity ────────────────────────────────────────────────────────── + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- file/dir/symlink/block/char/fifo/socket + + -- ── Content hash ────────────────────────────────────────────────────── + md5_hash TEXT, -- NULL for non-regular files + + -- ── lstat(2) — all raw os.lstat() fields packed as a JSON object ────── + -- Keys: st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, + -- st_atime, st_mtime, st_ctime, st_blocks, st_blksize + lstat TEXT NOT NULL, + + -- ── Human-readable permission strings ───────────────────────────────── + permissions_octal TEXT, -- e.g. "0o755" + permissions_str TEXT, -- e.g. "rwxr-xr-x" + owner_name TEXT, + group_name TEXT, + + -- ── Special bits ────────────────────────────────────────────────────── + setuid INTEGER DEFAULT 0, + setgid INTEGER DEFAULT 0, + sticky INTEGER DEFAULT 0, + + -- ── ACL (POSIX) ─────────────────────────────────────────────────────── + acl_text TEXT, -- raw getfacl output + + -- ── Extended attributes ─────────────────────────────────────────────── + xattrs TEXT, -- JSON dict of xattr key→value + + -- ── Symlink target ─────────────────────────────────────────────────── + symlink_target TEXT, + + -- ── SELinux / AppArmor context ──────────────────────────────────────── + security_context TEXT +); + +CREATE INDEX IF NOT EXISTS idx_file_entries_root + ON file_entries(root_folder, version_id); + +CREATE INDEX IF NOT EXISTS idx_file_entries_path + ON file_entries(root_folder, relative_path); + +CREATE TABLE IF NOT EXISTS staging ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + root_folder TEXT NOT NULL, + absolute_path TEXT NOT NULL, + added_at TEXT NOT NULL, -- ISO-8601 timestamp + UNIQUE(root_folder, absolute_path) +); +""" + + +def open_db(root_folder: str) -> sqlite3.Connection: + snaps = os.path.join(root_folder, SNAPSHOTS_DIR) + os.makedirs(snaps, exist_ok=True) + db_path = os.path.join(snaps, DB_NAME) + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA foreign_keys = ON") + conn.execute("PRAGMA journal_mode = WAL") + conn.executescript(SCHEMA) + conn.commit() + return conn diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py new file mode 100644 index 00000000..a70aeda8 --- /dev/null +++ b/tools/version_control/vcs_metadata_helper.py @@ -0,0 +1,248 @@ +import os +import stat +import hashlib +import sqlite3 +import json +import subprocess +import pwd +import grp +import sys +from typing import Optional + +# ─────────────────────────── METADATA HELPERS ──────────────────────────────── + +def file_type_str(mode: int) -> str: + if stat.S_ISREG(mode): return "file" + if stat.S_ISDIR(mode): return "dir" + if stat.S_ISLNK(mode): return "symlink" + if stat.S_ISBLK(mode): return "block" + if stat.S_ISCHR(mode): return "char" + if stat.S_ISFIFO(mode): return "fifo" + if stat.S_ISSOCK(mode): return "socket" + return "unknown" + + +def permission_str(mode: int) -> str: + """Convert mode bits → rwxrwxrwx string (no file-type prefix).""" + chars = [] + for who in (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, + stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, + stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH): + chars.append("+" if mode & who else "-") + # Replace x with s/t for setuid/setgid/sticky + if mode & stat.S_ISUID: + chars[2] = "s" if chars[2] == "+" else "S" + if mode & stat.S_ISGID: + chars[5] = "s" if chars[5] == "+" else "S" + if mode & stat.S_ISVTX: + chars[8] = "t" if chars[8] == "+" else "T" + return "".join(chars) + + +def owner_name(uid: int) -> str: + try: return pwd.getpwuid(uid).pw_name + except KeyError: return str(uid) + + +def group_name(gid: int) -> str: + try: return grp.getgrgid(gid).gr_name + except KeyError: return str(gid) + + +def get_acl(path: str) -> Optional[str]: + """Return raw getfacl output, or None if unavailable.""" + try: + if sys.platform == "darwin": + result = subprocess.run( + # ["getfacl", "--omit-header", "--absolute-names", path], + ["ls", "-le", path], + capture_output=True, text=True, timeout=5 + ) + lines = result.stdout.splitlines() + + acl_entries = [] + + for line in lines: + line = line.strip() + if line.startswith(tuple(str(i) + ":" for i in range(10))): + # Example line: "0: user:azad allow read,write" + parts = line.split("user:") + if len(parts) > 1: + acl_part = parts[1].strip() + acl_entries.append(acl_part) + + return ";".join(acl_entries) if acl_entries else None + else: + result = subprocess.run( + ["getfacl", "--omit-header", "--absolute-names", path], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() or None + except (FileNotFoundError, subprocess.TimeoutExpired): + return None + + +def set_acl(path: str, acl_string: str) -> Optional[str]: + """Return raw getfacl output, or None if unavailable.""" + acl_list = acl_string.split(";") + for acl_entry in acl_list: + try: + acls = acl_entry.split() + acl_perm = "user:" + acls[0] + " allow " + acls[2] + print(acl_perm) + result = subprocess.run( + ["chmod", "+a", acl_perm, path], + capture_output=True, text=True, timeout=5 + ) + if result.returncode != 0: + print(f"Failed to set ACL entry '{acl_entry}' on {path}: {result.stderr}") + except Exception as e: + print(f"Error setting ACL entry '{acl_entry}' on {path}: {e}") + + +def get_xattrs(path: str) -> Optional[str]: + """Return JSON dict of extended attributes, or None.""" + try: + import xattr # pyxattr + attrs = {} + for key in xattr.listxattr(path, symlink=True): + try: + val = xattr.getxattr(path, key, symlink=True) + attrs[key] = val.decode("utf-8", errors="replace") + except OSError: + attrs[key] = "" + return json.dumps(attrs) if attrs else None + except ImportError: + # Fallback: use getfattr + try: + result = subprocess.run( + ["getfattr", "-d", "-m", "-", "--absolute-names", path], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() or None + except FileNotFoundError: + return None + except OSError: + return None + + +def get_security_context(path: str) -> Optional[str]: + """Return SELinux/AppArmor security context if available.""" + try: + result = subprocess.run( + ["ls", "-Z", path], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + parts = result.stdout.strip().split() + # ls -Z format: context file (or just file if no context) + if len(parts) >= 2 and ":" in parts[0]: + return parts[0] + return None + except Exception: + return None + + +def md5_hash(path: str, chunk_size: int = 1 << 20) -> Optional[str]: + """Stream-hash a file in 1 MB chunks. Returns None on error.""" + h = hashlib.md5() + try: + with open(path, "rb") as f: + while chunk := f.read(chunk_size): + h.update(chunk) + return h.hexdigest() + except (PermissionError, OSError): + return None + + +def collect_metadata(abs_path: str, root_folder: str) -> dict: + """ + Gather all available metadata for a single path. + + Returns a dict with two logical groups: + • lstat — raw os.lstat() result stored as a nested dict (→ JSON column) + • all other keys — each stored in its own column + """ + rel_path = os.path.relpath(abs_path, root_folder) + + try: + s = os.lstat(abs_path) + except OSError as e: + return {"relative_path": rel_path, "error": str(e)} + + mode = s.st_mode + ftype = file_type_str(mode) + + # ── lstat dict (raw kernel values only) ────────────────────────────────── + lstat_dict = { + "st_mode": mode, + "st_ino": s.st_ino, + "st_dev": s.st_dev, + "st_nlink": s.st_nlink, + "st_uid": s.st_uid, + "st_gid": s.st_gid, + "st_size": s.st_size, + "st_atime": s.st_atime, + "st_mtime": s.st_mtime, + "st_ctime": s.st_ctime, + "st_blocks": getattr(s, "st_blocks", None), + "st_blksize": getattr(s, "st_blksize", None), + } + + # ── all other (derived / external) attributes — own columns ────────────── + entry = { + "relative_path": rel_path, + "absolute_path": abs_path, + "file_name": os.path.basename(abs_path), + "file_type": ftype, + + # serialised lstat dict → stored as TEXT (JSON) in a single column + "lstat": json.dumps(lstat_dict), + + # human-readable permission strings + "permissions_octal": oct(stat.S_IMODE(mode)), + "permissions_str": permission_str(mode), + "owner_name": owner_name(s.st_uid), + "group_name": group_name(s.st_gid), + + # special-bit flags + "setuid": int(bool(mode & stat.S_ISUID)), + "setgid": int(bool(mode & stat.S_ISGID)), + "sticky": int(bool(mode & stat.S_ISVTX)), + + # external / subprocess-derived attributes + "acl_text": get_acl(abs_path), + "xattrs": get_xattrs(abs_path), + "security_context": get_security_context(abs_path), + "symlink_target": os.readlink(abs_path) if ftype == "symlink" else None, + + # content hash — regular files only + "md5_hash": md5_hash(abs_path) if ftype == "file" else None, + + # convenience shortcut used by walk_folder aggregation (not stored) + "_st_size": s.st_size, + } + print(f"ACL text for {rel_path}: {entry['acl_text']}") + return entry + + +# def walk_folder(root_folder: str) -> list[dict]: +# """Recursively collect metadata for every entry under root_folder.""" +# entries = [] +# skip_dirs = {DB_NAME, SNAPSHOTS_DIR} + +# for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): +# # Prune internal bookkeeping dirs +# dirnames[:] = [d for d in dirnames if d not in skip_dirs] + +# # Include the directory entry itself (except root) +# if dirpath != root_folder: +# entries.append(collect_metadata(dirpath, root_folder)) + +# for fname in filenames: +# if fname == DB_NAME: +# continue +# abs_path = os.path.join(dirpath, fname) +# entries.append(collect_metadata(abs_path, root_folder)) + +# return entries From 3455edb22fbbf854a085dc8751632c101b897cf2 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 26 Mar 2026 19:25:27 -0600 Subject: [PATCH 037/109] Fixed acl in versioning --- tools/version_control/vcs_metadata_helper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index a70aeda8..4eae1bcd 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -87,6 +87,8 @@ def set_acl(path: str, acl_string: str) -> Optional[str]: acl_list = acl_string.split(";") for acl_entry in acl_list: try: + if len(acl_entry) == 0: + continue acls = acl_entry.split() acl_perm = "user:" + acls[0] + " allow " + acls[2] print(acl_perm) From b8fb3d3ec50b9bd6776913300fddad40c4ff6f64 Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 08:54:51 -0600 Subject: [PATCH 038/109] VCS: Fix permission str and cli usage. --- tools/version_control/dsi_vcs.py | 22 +++++++++++--------- tools/version_control/vcs_metadata_helper.py | 17 ++++++++------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index 44ebaa56..80a1e551 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -5,11 +5,13 @@ and stores versioned snapshots in SQLite. Usage: - python dsi_vcs.py init # init repo - python dsi_vcs.py commit [message] # commit a new version - python dsi_vcs.py log # list versions - python dsi_vcs.py diff # diff two versions - python dsi_vcs.py restore # restore a version + python dsi_vcs.py init # init repo in current directory + python dsi_vcs.py add ... # stage paths for the next commit + python dsi_vcs.py remove ... # unstage paths + python dsi_vcs.py commit [message] # commit a new version + python dsi_vcs.py log # list versions + python dsi_vcs.py diff # diff two versions + python dsi_vcs.py restore # restore a version Requirements: pip install pyxattr # for extended attributes @@ -176,7 +178,7 @@ def stage_path(abs_path: str): for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") - + def cmd_remove(self, paths: list[str]): """Remove file(s) from the staging area without touching the actual files.""" root_folder = os.path.abspath(self.root_folder) @@ -483,7 +485,6 @@ def main(): p_diff.add_argument("c2") p_restore = sub.add_parser("restore", help="Restore a version") - p_restore.add_argument("root_folder") p_restore.add_argument("version") args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) @@ -507,10 +508,11 @@ def main(): elif args.command == "log": vcs = DSIVCS(os.getcwd()) vcs.cmd_log() - # elif args.command == "diff": cmd_diff(args.root_folder, args.v1, args.v2) - # elif args.command == "restore": cmd_restore(args.root_folder, args.version) + elif args.command == "restore": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_restore(args.version) if __name__ == "__main__": # print("\n=== dsi-vcs: rsync-based file version control ===\n") # print(os.getcwd()) - main() \ No newline at end of file + main() diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 4eae1bcd..6daea75b 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -25,17 +25,19 @@ def file_type_str(mode: int) -> str: def permission_str(mode: int) -> str: """Convert mode bits → rwxrwxrwx string (no file-type prefix).""" chars = [] - for who in (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, - stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, - stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH): - chars.append("+" if mode & who else "-") + for bit, char in ( + (stat.S_IRUSR, "r"), (stat.S_IWUSR, "w"), (stat.S_IXUSR, "x"), + (stat.S_IRGRP, "r"), (stat.S_IWGRP, "w"), (stat.S_IXGRP, "x"), + (stat.S_IROTH, "r"), (stat.S_IWOTH, "w"), (stat.S_IXOTH, "x"), + ): + chars.append(char if mode & bit else "-") # Replace x with s/t for setuid/setgid/sticky if mode & stat.S_ISUID: - chars[2] = "s" if chars[2] == "+" else "S" + chars[2] = "s" if chars[2] == "x" else "S" if mode & stat.S_ISGID: - chars[5] = "s" if chars[5] == "+" else "S" + chars[5] = "s" if chars[5] == "x" else "S" if mode & stat.S_ISVTX: - chars[8] = "t" if chars[8] == "+" else "T" + chars[8] = "t" if chars[8] == "x" else "T" return "".join(chars) @@ -224,7 +226,6 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: # convenience shortcut used by walk_folder aggregation (not stored) "_st_size": s.st_size, } - print(f"ACL text for {rel_path}: {entry['acl_text']}") return entry From 5275adf448ebbe59e41487b18501c81b4f96407e Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 09:14:55 -0600 Subject: [PATCH 039/109] VCS: create complete snapshots. The rel_path branch results in only including staged files in the snapshot. Complete snapshots of the remote folder are needed so unchanged files are not omitted in a snapshot. --- tools/version_control/dsi_vcs.py | 71 ++++++++------------ tools/version_control/vcs_metadata_helper.py | 19 ++++++ 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index 80a1e551..ef185f50 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -38,15 +38,11 @@ def rsync_snapshot( root_folder: str, dest_path: str, prev_snapshot: Optional[str] = None, - rel_paths: Optional[list[str]] = None, ) -> bool: """ - Copy files from root_folder → dest_path using rsync hard-link deduplication. - If rel_paths is given, only those paths are synced (via --files-from). + Copy the full root_folder tree to dest_path using rsync hard-link deduplication. If prev_snapshot is provided, unchanged files are hard-linked (saves disk). """ - import tempfile - os.makedirs(dest_path, exist_ok=True) cmd = ["rsync", "-aAXH"] # for linux if sys.platform == "darwin": @@ -55,38 +51,22 @@ def rsync_snapshot( if prev_snapshot and os.path.isdir(prev_snapshot): cmd += ["--link-dest", os.path.abspath(prev_snapshot)] - # Write a temp file-list when only staging specific paths - files_from_tmp = None - if rel_paths is not None: - tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) - tmp.write("\n".join(rel_paths)) - tmp.close() - files_from_tmp = tmp.name - cmd += ["--files-from", files_from_tmp] - else: - cmd += [ - "--delete", - "--exclude", DB_NAME, - "--exclude", SNAPSHOTS_DIR, - ] + cmd += [ + "--delete", + "--exclude", DB_NAME, + "--exclude", SNAPSHOTS_DIR, + ] cmd += [ root_folder.rstrip("/") + "/", dest_path.rstrip("/") + "/", ] - try: - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode not in (0, 24): # 24 = some files vanished (ok) - print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) - return False - if sys.platform == "darwin": - for rel_path in rel_paths: - set_acl(dest_path, get_acl(rel_path) or "") - return True - finally: - if files_from_tmp: - os.unlink(files_from_tmp) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode not in (0, 24): # 24 = some files vanished (ok) + print(f"[rsync error] {result.stderr.strip()}", file=sys.stderr) + return False + return True # ─────────────────────────── COMMANDS ──────────────────────────────────────── @@ -259,33 +239,38 @@ def cmd_commit(self, message: str = ""): running_user = owner_name(os.getuid()) snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) - # ── Collect metadata for each staged file ─────────────────────────────── - print(f"Collecting metadata for {len(staged_paths)} staged file(s)…") - entries = [] + # ── Validate staged files before creating the snapshot ───────────────── + print(f"Validating {len(staged_paths)} staged file(s)…") + valid_staged = 0 for abs_path in staged_paths: e = collect_metadata(abs_path, self.root_folder) if "error" in e: print(f" [skip] {e['relative_path']}: {e['error']}") else: - entries.append(e) + valid_staged += 1 - if not entries: + if valid_staged == 0: conn.close() sys.exit("No readable staged files — commit aborted.") - total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") - file_count = sum(1 for e in entries if e.get("file_type") == "file") - - # ── rsync only the staged relative paths ──────────────────────────────── - rel_paths = [e["relative_path"] for e in entries] - print(f" {file_count} file(s), {total_bytes:,} bytes") + # ── Create a complete snapshot of the current repository tree ─────────── print(f" Creating rsync snapshot → {snapshot_path}") - ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot, rel_paths) + ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot) if not ok: conn.close() sys.exit("rsync failed — commit aborted.") + # ── Collect metadata for the complete committed tree ─────────────────── + entries = collect_root_file_metadata(self.root_folder) + if not entries: + conn.close() + sys.exit("No readable files — commit aborted.") + + total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") + file_count = sum(1 for e in entries if e.get("file_type") == "file") + print(f" {file_count} file(s), {total_bytes:,} bytes") + # ── Insert version row ─────────────────────────────────────────────────── cur.execute( """INSERT INTO versions diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 6daea75b..122b765c 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,6 +229,25 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry +def collect_root_file_metadata(root_folder: str) -> list[dict]: + """Collect metadata for every tracked file under root_folder.""" + entries = [] + skip_names = {DB_NAME, SNAPSHOTS_DIR} + + for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in skip_names] + for fname in filenames: + if fname in skip_names: + continue + entry = collect_metadata(os.path.join(dirpath, fname), root_folder) + if "error" in entry: + print(f" [skip] {entry['relative_path']}: {entry['error']}") + else: + entries.append(entry) + + return entries + + # def walk_folder(root_folder: str) -> list[dict]: # """Recursively collect metadata for every entry under root_folder.""" # entries = [] From e945c5ccae841bac1684d2382ceea5867fa36c6d Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 09:26:24 -0600 Subject: [PATCH 040/109] VCS: track directory metadata. Previous metadata tracking was only for files. This tracks paths (both files and directories) so we can preserve permissions for directory trees. --- tools/version_control/dsi_vcs.py | 23 ++++++++++---------- tools/version_control/vcs_metadata_helper.py | 12 ++++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index ef185f50..a5476aa0 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -130,6 +130,7 @@ def stage_path(abs_path: str): # Expand directory recursively for dirpath, dirnames, filenames in os.walk(abs_path, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] + stage_path(dirpath) for fname in filenames: if fname in skip_names: continue @@ -139,7 +140,7 @@ def stage_path(abs_path: str): conn.commit() conn.close() - print(f" {staged} file(s) added to staging.") + print(f" {staged} path(s) added to staging.") """Show files currently in the staging area.""" root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) @@ -151,16 +152,16 @@ def stage_path(abs_path: str): conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage files.") + print("Nothing staged. Use 'add ...' to stage paths.") return - print(f"Staged files ({len(rows)}):") + print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") def cmd_remove(self, paths: list[str]): - """Remove file(s) from the staging area without touching the actual files.""" + """Remove path(s) from the staging area without touching the actual files.""" root_folder = os.path.abspath(self.root_folder) db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) if not os.path.isfile(db_path): @@ -186,7 +187,7 @@ def cmd_remove(self, paths: list[str]): conn.commit() conn.close() - print(f" {removed} file(s) removed from staging.") + print(f" {removed} path(s) removed from staging.") """Show files currently in the staging area.""" root_folder = os.path.abspath(root_folder) conn = open_db(root_folder) @@ -198,10 +199,10 @@ def cmd_remove(self, paths: list[str]): conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage files.") + print("Nothing staged. Use 'add ...' to stage paths.") return - print(f"Staged files ({len(rows)}):") + print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) print(f" {rel}") @@ -239,8 +240,8 @@ def cmd_commit(self, message: str = ""): running_user = owner_name(os.getuid()) snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) - # ── Validate staged files before creating the snapshot ───────────────── - print(f"Validating {len(staged_paths)} staged file(s)…") + # ── Validate staged paths before creating the snapshot ───────────────── + print(f"Validating {len(staged_paths)} staged path(s)…") valid_staged = 0 for abs_path in staged_paths: e = collect_metadata(abs_path, self.root_folder) @@ -251,7 +252,7 @@ def cmd_commit(self, message: str = ""): if valid_staged == 0: conn.close() - sys.exit("No readable staged files — commit aborted.") + sys.exit("No readable staged paths — commit aborted.") # ── Create a complete snapshot of the current repository tree ─────────── print(f" Creating rsync snapshot → {snapshot_path}") @@ -262,7 +263,7 @@ def cmd_commit(self, message: str = ""): sys.exit("rsync failed — commit aborted.") # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_root_file_metadata(self.root_folder) + entries = collect_root_metadata(self.root_folder) if not entries: conn.close() sys.exit("No readable files — commit aborted.") diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 122b765c..819bf100 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,13 +229,21 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry -def collect_root_file_metadata(root_folder: str) -> list[dict]: - """Collect metadata for every tracked file under root_folder.""" + +def collect_root_metadata(root_folder: str) -> list[dict]: + """Collect metadata for every tracked directory and file under root_folder.""" entries = [] skip_names = {DB_NAME, SNAPSHOTS_DIR} for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] + if dirpath != root_folder: + entry = collect_metadata(dirpath, root_folder) + if "error" in entry: + print(f" [skip] {entry['relative_path']}: {entry['error']}") + else: + entries.append(entry) + for fname in filenames: if fname in skip_names: continue From f2ff6b51bed18a3b4fb206a7ea66b6570aebec4a Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 10:19:56 -0600 Subject: [PATCH 041/109] VCS: add delete command for staging path deletion. --- tools/version_control/dsi_vcs.py | 127 +++++++++++++++---- tools/version_control/vcs_db.py | 1 + tools/version_control/vcs_metadata_helper.py | 16 ++- 3 files changed, 109 insertions(+), 35 deletions(-) diff --git a/tools/version_control/dsi_vcs.py b/tools/version_control/dsi_vcs.py index a5476aa0..53901553 100755 --- a/tools/version_control/dsi_vcs.py +++ b/tools/version_control/dsi_vcs.py @@ -7,6 +7,7 @@ Usage: python dsi_vcs.py init # init repo in current directory python dsi_vcs.py add ... # stage paths for the next commit + python dsi_vcs.py delete ... # stage paths for deletion python dsi_vcs.py remove ... # unstage paths python dsi_vcs.py commit [message] # commit a new version python dsi_vcs.py log # list versions @@ -26,6 +27,7 @@ import json import datetime import argparse +import shutil from pathlib import Path from typing import Optional @@ -69,6 +71,26 @@ def rsync_snapshot( return True +def snapshot_target(snapshot_path: str, relative_path: str) -> str: + target = os.path.abspath(os.path.join(snapshot_path, relative_path)) + snapshot_root = os.path.abspath(snapshot_path) + if os.path.commonpath([snapshot_root, target]) != snapshot_root: + raise ValueError(f"Snapshot path escapes snapshot root: {relative_path}") + return target + + +def apply_snapshot_deletes(snapshot_path: str, root_folder: str, staged_deletes: list[str]) -> None: + for abs_path in staged_deletes: + rel_path = os.path.relpath(abs_path, root_folder) + if rel_path in ("", "."): + raise ValueError("Refusing to stage repository root for deletion.") + target = snapshot_target(snapshot_path, rel_path) + if os.path.isdir(target) and not os.path.islink(target): + shutil.rmtree(target) + elif os.path.lexists(target): + os.unlink(target) + + # ─────────────────────────── COMMANDS ──────────────────────────────────────── class DSIVCS(): @@ -104,27 +126,22 @@ def cmd_add(self, paths: list[str]): def stage_path(abs_path: str): nonlocal staged - # Reject paths outside the root - try: - os.path.relpath(abs_path, self.root_folder) - except ValueError: - print(f" [skip] {abs_path}: outside root folder") - return + abs_path = os.path.abspath(abs_path) if not os.path.lexists(abs_path): print(f" [skip] {abs_path}: path does not exist") return cur.execute( - "INSERT OR IGNORE INTO staging (root_folder, absolute_path, added_at) " - "VALUES (?, ?, ?)", - (self.root_folder, abs_path, added_at) + "INSERT OR REPLACE INTO staging (root_folder, absolute_path, action, added_at) " + "VALUES (?, ?, ?, ?)", + (self.root_folder, abs_path, "add", added_at) ) if cur.rowcount: staged += 1 for raw in paths: - abs_path = os.path.join(self.root_folder, raw) + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(self.root_folder, raw)) if os.path.isdir(abs_path): # Expand directory recursively @@ -145,20 +162,63 @@ def stage_path(abs_path: str): root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) rows = conn.execute( - "SELECT absolute_path, added_at FROM staging " + "SELECT absolute_path, action, added_at FROM staging " "WHERE root_folder=? ORDER BY absolute_path", (root_folder,) ).fetchall() conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage paths.") + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") return print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) - print(f" {rel}") + print(f" {rel} [{r['action']}]") + + def cmd_delete(self, paths: list[str]): + """Stage path(s) for deletion in the next commit.""" + db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) + if not os.path.isfile(db_path): + sys.exit("No dsi-vcs repo found. Run 'init' first.") + + conn = open_db(self.root_folder) + cur = conn.cursor() + added_at = datetime.datetime.now(datetime.timezone.utc).isoformat() + staged = 0 + + for raw in paths: + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(self.root_folder, raw)) + cur.execute( + "INSERT OR REPLACE INTO staging (root_folder, absolute_path, action, added_at) " + "VALUES (?, ?, ?, ?)", + (self.root_folder, abs_path, "delete", added_at) + ) + if cur.rowcount: + staged += 1 + + conn.commit() + conn.close() + print(f" {staged} path(s) staged for deletion.") + """Show files currently in the staging area.""" + root_folder = os.path.abspath(self.root_folder) + conn = open_db(root_folder) + rows = conn.execute( + "SELECT absolute_path, action, added_at FROM staging " + "WHERE root_folder=? ORDER BY absolute_path", + (root_folder,) + ).fetchall() + conn.close() + + if not rows: + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") + return + + print(f"Staged paths ({len(rows)}):") + for r in rows: + rel = os.path.relpath(r["absolute_path"], root_folder) + print(f" {rel} [{r['action']}]") def cmd_remove(self, paths: list[str]): """Remove path(s) from the staging area without touching the actual files.""" @@ -172,7 +232,7 @@ def cmd_remove(self, paths: list[str]): removed = 0 for raw in paths: - abs_path = os.path.abspath(raw) + abs_path = os.path.abspath(raw if os.path.isabs(raw) else os.path.join(root_folder, raw)) cur.execute( "DELETE FROM staging WHERE root_folder=? AND absolute_path=?", (root_folder, abs_path) @@ -192,20 +252,20 @@ def cmd_remove(self, paths: list[str]): root_folder = os.path.abspath(root_folder) conn = open_db(root_folder) rows = conn.execute( - "SELECT absolute_path, added_at FROM staging " + "SELECT absolute_path, action, added_at FROM staging " "WHERE root_folder=? ORDER BY absolute_path", (root_folder,) ).fetchall() conn.close() if not rows: - print("Nothing staged. Use 'add ...' to stage paths.") + print("Nothing staged. Use 'add ...' or 'delete ...' to stage paths.") return print(f"Staged paths ({len(rows)}):") for r in rows: rel = os.path.relpath(r["absolute_path"], root_folder) - print(f" {rel}") + print(f" {rel} [{r['action']}]") def cmd_commit(self, message: str = ""): db_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, DB_NAME) @@ -217,15 +277,16 @@ def cmd_commit(self, message: str = ""): # ── Load staged paths ──────────────────────────────────────────────────── staged_rows = cur.execute( - "SELECT absolute_path FROM staging WHERE root_folder = ? ORDER BY absolute_path", + "SELECT absolute_path, action FROM staging WHERE root_folder = ? ORDER BY absolute_path", (self.root_folder,) ).fetchall() if not staged_rows: conn.close() - sys.exit("Nothing staged. Use 'add' to stage files before committing.") + sys.exit("Nothing staged. Use 'add' or 'delete' before committing.") - staged_paths = [r["absolute_path"] for r in staged_rows] + staged_adds = [r["absolute_path"] for r in staged_rows if r["action"] == "add"] + staged_deletes = [r["absolute_path"] for r in staged_rows if r["action"] == "delete"] # ── Previous snapshot for hard-link deduplication ──────────────────────── prev_row = cur.execute( @@ -241,9 +302,9 @@ def cmd_commit(self, message: str = ""): snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) # ── Validate staged paths before creating the snapshot ───────────────── - print(f"Validating {len(staged_paths)} staged path(s)…") - valid_staged = 0 - for abs_path in staged_paths: + print(f"Validating {len(staged_rows)} staged path(s)…") + valid_staged = len(staged_deletes) + for abs_path in staged_adds: e = collect_metadata(abs_path, self.root_folder) if "error" in e: print(f" [skip] {e['relative_path']}: {e['error']}") @@ -262,11 +323,15 @@ def cmd_commit(self, message: str = ""): conn.close() sys.exit("rsync failed — commit aborted.") - # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_root_metadata(self.root_folder) - if not entries: + try: + apply_snapshot_deletes(snapshot_path, self.root_folder, staged_deletes) + except ValueError as e: conn.close() - sys.exit("No readable files — commit aborted.") + shutil.rmtree(snapshot_path, ignore_errors=True) + sys.exit(str(e)) + + # ── Collect metadata for the complete committed tree ─────────────────── + entries = collect_tree_metadata(snapshot_path, self.root_folder) total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") file_count = sum(1 for e in entries if e.get("file_type") == "file") @@ -458,10 +523,13 @@ def main(): p_add = sub.add_parser("add", help="add file or directory to staging") p_add.add_argument('files', nargs='+', type=str) + p_delete = sub.add_parser("delete", help="stage file or directory deletion") + p_delete.add_argument('files', nargs='+', type=str) + p_remove = sub.add_parser("remove", help="remove file or directory from staging") p_remove.add_argument('files', nargs='+', type=str) - p_commit = sub.add_parser("commit", help="Snapshot and commit staged files") + p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) p_log = sub.add_parser("log", help="List all commits") @@ -482,6 +550,9 @@ def main(): elif args.command == "add": vcs = DSIVCS(os.getcwd()) vcs.cmd_add(args.files) + elif args.command == "delete": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_delete(args.files) elif args.command == "remove": vcs = DSIVCS(os.getcwd()) vcs.cmd_remove(args.files) diff --git a/tools/version_control/vcs_db.py b/tools/version_control/vcs_db.py index 72ef6990..aaf21fca 100644 --- a/tools/version_control/vcs_db.py +++ b/tools/version_control/vcs_db.py @@ -75,6 +75,7 @@ id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, absolute_path TEXT NOT NULL, + action TEXT NOT NULL DEFAULT 'add', added_at TEXT NOT NULL, -- ISO-8601 timestamp UNIQUE(root_folder, absolute_path) ); diff --git a/tools/version_control/vcs_metadata_helper.py b/tools/version_control/vcs_metadata_helper.py index 819bf100..a8734f7f 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/tools/version_control/vcs_metadata_helper.py @@ -229,33 +229,35 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: return entry - -def collect_root_metadata(root_folder: str) -> list[dict]: - """Collect metadata for every tracked directory and file under root_folder.""" +def collect_tree_metadata(scan_root: str, identity_root: str) -> list[dict]: + """Collect metadata under scan_root while storing paths under identity_root.""" entries = [] skip_names = {DB_NAME, SNAPSHOTS_DIR} - for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + for dirpath, dirnames, filenames in os.walk(scan_root, followlinks=False): dirnames[:] = [d for d in dirnames if d not in skip_names] - if dirpath != root_folder: - entry = collect_metadata(dirpath, root_folder) + if dirpath != scan_root: + entry = collect_metadata(dirpath, scan_root) if "error" in entry: print(f" [skip] {entry['relative_path']}: {entry['error']}") else: + entry["absolute_path"] = os.path.join(identity_root, entry["relative_path"]) entries.append(entry) for fname in filenames: if fname in skip_names: continue - entry = collect_metadata(os.path.join(dirpath, fname), root_folder) + entry = collect_metadata(os.path.join(dirpath, fname), scan_root) if "error" in entry: print(f" [skip] {entry['relative_path']}: {entry['error']}") else: + entry["absolute_path"] = os.path.join(identity_root, entry["relative_path"]) entries.append(entry) return entries + # def walk_folder(root_folder: str) -> list[dict]: # """Recursively collect metadata for every entry under root_folder.""" # entries = [] From 9e03619c0f50cade7fb0fa1375cc293638d0104c Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Thu, 23 Apr 2026 13:44:55 -0600 Subject: [PATCH 042/109] VCS: move dsi_vcs utility under dsi, begin tests. This moves the dsi_vcs into dsi as a package under utils so it will be installed and available. This also begins tests for the utility. --- dsi/utils/version_control/__init__.py | 3 + dsi/utils/version_control/__main__.py | 5 ++ dsi/utils/version_control/cli.py | 68 ++++++++++++++++++ .../utils}/version_control/dsi_vcs.py | 71 +------------------ .../utils}/version_control/requirements.txt | 0 dsi/utils/version_control/tests/test_vcs.py | 70 ++++++++++++++++++ .../utils}/version_control/vcs_db.py | 0 .../version_control/vcs_metadata_helper.py | 2 + pyproject.toml | 3 +- 9 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 dsi/utils/version_control/__init__.py create mode 100644 dsi/utils/version_control/__main__.py create mode 100644 dsi/utils/version_control/cli.py rename {tools => dsi/utils}/version_control/dsi_vcs.py (88%) rename {tools => dsi/utils}/version_control/requirements.txt (100%) create mode 100644 dsi/utils/version_control/tests/test_vcs.py rename {tools => dsi/utils}/version_control/vcs_db.py (100%) rename {tools => dsi/utils}/version_control/vcs_metadata_helper.py (99%) diff --git a/dsi/utils/version_control/__init__.py b/dsi/utils/version_control/__init__.py new file mode 100644 index 00000000..64b89704 --- /dev/null +++ b/dsi/utils/version_control/__init__.py @@ -0,0 +1,3 @@ +from .dsi_vcs import DSIVCS + +__all__ = ["DSIVCS"] diff --git a/dsi/utils/version_control/__main__.py b/dsi/utils/version_control/__main__.py new file mode 100644 index 00000000..2efe6923 --- /dev/null +++ b/dsi/utils/version_control/__main__.py @@ -0,0 +1,5 @@ +from .cli import main + +if __name__ == "__main__": + main() + diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py new file mode 100644 index 00000000..97823901 --- /dev/null +++ b/dsi/utils/version_control/cli.py @@ -0,0 +1,68 @@ +import argparse +import datetime +import sys + +from .dsi_vcs import DSIVCS + +def main(): + parser = argparse.ArgumentParser( + description="dsi-vcs — rsync-based file version control with full Linux metadata" + ) + sub = parser.add_subparsers(dest="command", required=True) + + p_init = sub.add_parser("init", help="Initialize a new repo") + # p_init.add_argument("root_folder") + + p_add = sub.add_parser("add", help="add file or directory to staging") + p_add.add_argument('files', nargs='+', type=str) + + p_delete = sub.add_parser("delete", help="stage file or directory deletion") + p_delete.add_argument('files', nargs='+', type=str) + + p_remove = sub.add_parser("remove", help="remove file or directory from staging") + p_remove.add_argument('files', nargs='+', type=str) + + p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") + p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) + + p_log = sub.add_parser("log", help="List all commits") + + p_diff = sub.add_parser("diff", help="Diff two commits") + p_diff.add_argument("c1") + p_diff.add_argument("c2") + + p_restore = sub.add_parser("restore", help="Restore a version") + p_restore.add_argument("version") + + args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) + + # parser.print_help() + # parser.format_help() + if args.command == "init": + vcs = DSIVCS(os.getcwd()) + elif args.command == "add": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_add(args.files) + elif args.command == "delete": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_delete(args.files) + elif args.command == "remove": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_remove(args.files) + elif args.command == "commit": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_commit(args.message) + elif args.command == "diff": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_diff(args.c1, args.c2) + elif args.command == "log": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_log() + elif args.command == "restore": + vcs = DSIVCS(os.getcwd()) + vcs.cmd_restore(args.version) + +if __name__ == "__main__": + # print("\n=== dsi-vcs: rsync-based file version control ===\n") + # print(os.getcwd()) + main() diff --git a/tools/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py similarity index 88% rename from tools/version_control/dsi_vcs.py rename to dsi/utils/version_control/dsi_vcs.py index 53901553..bc30538a 100755 --- a/tools/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -26,13 +26,12 @@ import subprocess import json import datetime -import argparse import shutil from pathlib import Path from typing import Optional -from vcs_db import * -from vcs_metadata_helper import * +from .vcs_db import * +from .vcs_metadata_helper import * # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -507,69 +506,3 @@ def cmd_restore(self, commit_hash: str): if result.returncode not in (0, 24): sys.exit(f"rsync restore failed:\n{result.stderr}") print(f" Restored to {full_hash}") - - -# ─────────────────────────── CLI ───────────────────────────────────────────── - -def main(): - parser = argparse.ArgumentParser( - description="dsi_vcs — rsync-based file version control with full Linux metadata" - ) - sub = parser.add_subparsers(dest="command", required=True) - - p_init = sub.add_parser("init", help="Initialize a new repo") - # p_init.add_argument("root_folder") - - p_add = sub.add_parser("add", help="add file or directory to staging") - p_add.add_argument('files', nargs='+', type=str) - - p_delete = sub.add_parser("delete", help="stage file or directory deletion") - p_delete.add_argument('files', nargs='+', type=str) - - p_remove = sub.add_parser("remove", help="remove file or directory from staging") - p_remove.add_argument('files', nargs='+', type=str) - - p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") - p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) - - p_log = sub.add_parser("log", help="List all commits") - - p_diff = sub.add_parser("diff", help="Diff two commits") - p_diff.add_argument("c1") - p_diff.add_argument("c2") - - p_restore = sub.add_parser("restore", help="Restore a version") - p_restore.add_argument("version") - - args = parser.parse_args(args=None if sys.argv[1:] else ["-h"]) - - # parser.print_help() - # parser.format_help() - if args.command == "init": - vcs = DSIVCS(os.getcwd()) - elif args.command == "add": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_add(args.files) - elif args.command == "delete": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_delete(args.files) - elif args.command == "remove": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_remove(args.files) - elif args.command == "commit": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_commit(args.message) - elif args.command == "diff": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_diff(args.c1, args.c2) - elif args.command == "log": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_log() - elif args.command == "restore": - vcs = DSIVCS(os.getcwd()) - vcs.cmd_restore(args.version) - -if __name__ == "__main__": - # print("\n=== dsi-vcs: rsync-based file version control ===\n") - # print(os.getcwd()) - main() diff --git a/tools/version_control/requirements.txt b/dsi/utils/version_control/requirements.txt similarity index 100% rename from tools/version_control/requirements.txt rename to dsi/utils/version_control/requirements.txt diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py new file mode 100644 index 00000000..c1b961ba --- /dev/null +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -0,0 +1,70 @@ +import os +import sqlite3 +from pathlib import Path +from shutil import which +import pytest +import stat + +from dsi.utils.version_control.dsi_vcs import DSIVCS +from dsi.utils.version_control.vcs_db import DB_NAME, SNAPSHOTS_DIR + +def require_rsync(): + if which("rsync") is None: + pytest.skip("rsync is required for dsi_vcs.") + + + +def connect_repo(repo_path): + return sqlite3.connect(repo_path / SNAPSHOTS_DIR / DB_NAME) + +def commits(repo_path): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return conn.execute( + "SELECT id, commit_hash, snapshot_path FROM versions ORDER BY id" + ).fetchall() + +def latest_entries(repo_path): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return conn.execute( + "SELECT relative_path, file_type, permissions_octal, permissions_str, setgid " + "FROM file_entries WHERE version_id=(SELECT MAX(id) from versions) " + "ORDER BY relative_path").fetchall() + +def test_add(tmp_path): + require_rsync() + repo = DSIVCS(str(tmp_path)) + + empty_dir = tmp_path / "empty" + nested_dir = tmp_path / "nested" / "child" + empty_dir.mkdir() + nested_dir.mkdir(parents=True) + (tmp_path / "a.txt").write_text("foo") + (nested_dir / "b.txt").write_text("bar") + + empty_dir.chmod(0o2775) + + repo.cmd_add(["a.txt", "nested", "empty"]) + repo.cmd_commit("alpha") + + (tmp_path / "c.txt").write_text("baz") + + repo.cmd_add(["c.txt"]) + repo.cmd_commit("beta") + + alpha, beta = commits(tmp_path) + alpha_path = Path(alpha["snapshot_path"]) + beta_path = Path(beta["snapshot_path"]) + assert (alpha_path / "a.txt").read_text() == "foo" + assert (alpha_path / "nested" / "child" / "b.txt").read_text() == "bar" + assert (beta_path / "c.txt").read_text() == "baz" + assert (alpha_path / "empty").exists() + assert stat.S_IMODE((alpha_path / "empty").stat().st_mode) == 0o2775 + + rows = {row["relative_path"]: row for row in latest_entries(tmp_path)} + empty_entry = rows["empty"] + assert empty_entry["file_type"] == "dir" + assert empty_entry["permissions_str"] == "rwxrwsr-x" + assert eval(empty_entry["permissions_octal"]) == 0o2775 + assert empty_entry["setgid"] == 1 diff --git a/tools/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py similarity index 100% rename from tools/version_control/vcs_db.py rename to dsi/utils/version_control/vcs_db.py diff --git a/tools/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py similarity index 99% rename from tools/version_control/vcs_metadata_helper.py rename to dsi/utils/version_control/vcs_metadata_helper.py index a8734f7f..9b756f3b 100644 --- a/tools/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -9,6 +9,8 @@ import sys from typing import Optional +from .vcs_db import DB_NAME, SNAPSHOTS_DIR + # ─────────────────────────── METADATA HELPERS ──────────────────────────────── def file_type_str(mode: int) -> str: diff --git a/pyproject.toml b/pyproject.toml index 11c6bbaa..c53ca221 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ include-package-data = true [project.scripts] dsi = "dsi.cli:main" +dsi-vcs = "dsi.utils.version_control.cli:main" [tool.setuptools.packages.find] where = ["."] @@ -36,4 +37,4 @@ include = ["dsi*"] exclude = ["clover3d"] [tool.setuptools.package-data] -dsi = ["utils/launch_streamlit.sh","utils/launch_streamlit.bat"] \ No newline at end of file +dsi = ["utils/launch_streamlit.sh","utils/launch_streamlit.bat"] From 19eda2682171a98840dd9ccbb7626e9070eedd7c Mon Sep 17 00:00:00 2001 From: Andrew Reisner Date: Fri, 24 Apr 2026 12:33:22 -0600 Subject: [PATCH 043/109] VCS: begin stubbing vcs interface in core. --- dsi/core.py | 38 +++++++++++++++++++++++++++++++- dsi/tests/test_core.py | 50 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/dsi/core.py b/dsi/core.py index 2149f463..697b1ac2 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1410,4 +1410,40 @@ def can_create_file_here(self, dir = "."): pass return True except (PermissionError, OSError): - return False \ No newline at end of file + return False + + def vcs_init(self, repo_path): + """Initialize VCS repo." + pass + + def vcs_status(self): + """Return vcs status metadata for the first loaded backend.""" + pass + + def vcs_commit(self, message, author_name=None, author_email=None): + """Commit the current versioned backend state.""" + pass + + def vcs_diff(self, old_ref=None, new_ref=None, cached=False): + """Return the git diff for the first loaded backend.""" + pass + + def vcs_log(self, limit=None, ref='HEAD'): + """Return commit history for the first loaded backend.""" + pass + + def vcs_current_branch(self): + """Return the current branch for the first loaded backend.""" + pass + + def vcs_list_branches(self): + """Return local branches for the first loaded backend.""" + pass + + def vcs_create_branch(self, name, start_point='HEAD', checkout=False, force=False): + """Create a branch on the first loaded backend.""" + pass + + def vcs_checkout_branch(self, name, force=False): + """Checkout a branch on the first loaded backend.""" + pass diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index 982346bb..eb366857 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -20,6 +20,54 @@ def test_unload_module(): a.unload_module('plugin', 'GitInfo', 'writer') assert len(a.list_loaded_modules()['writer']) == 0 +def test_sqlite_versioning(tmp_path): + a = Terminal() + dbpath = tmp_path / "core_versioned.db" + # dbpath = '/tmp/versionined.db' + + a.load_module( + 'plugin', + 'Dict', + 'reader', + collection=OrderedDict({"wildfire": OrderedDict({'foo': [1], 'bar': [3]})}), + ) + a.load_module('backend', 'Sqlite', 'back-write', filename=str(dbpath)) + a.artifact_handler(interaction_type='ingest') + + a.vcs_init(tmp_path) + + # print(a.vcs_current_branch()) + # print(a.vcs_status()) + # status = a.vcs_status() + # assert status['branch'] == 'main' + # assert status['is_dirty'] is True + + # commit_id = a.vcs_commit('initial snapshot') + # assert len(commit_id) == 40 + # assert a.vcs_status()['is_dirty'] is False + # assert a.vcs_log(limit=1)[0]['message'] == 'initial snapshot' + # print(a.vcs_log()) + + # a.vcs_create_branch('feature', checkout=True) + # assert a.vcs_current_branch() == 'feature' + # print(a.vcs_list_branches()) + + # a.update_abstraction('wildfire', OrderedDict({'foo': [1, 2], 'bar': [3, 4]})) + # a.artifact_handler(interaction_type='ingest') + # diff = a.vcs_diff() + # print(diff) + # assert '+INSERT INTO \"wildfire\" VALUES(2,4);' in diff + + # a.vcs_commit('feature snapshot') + + # a.vcs_checkout_branch('main') + # table = a.get_table('wildfire') + # assert table.values.tolist() == [[1, 3]] + # assert sorted(a.vcs_list_branches()) == ['feature', 'main'] + + a.close() + + # SQLITE TESTS def test_ingest_sqlite_backend(): a = Terminal() @@ -980,4 +1028,4 @@ def test_test_sanitize_input(): !jack1 | 2.0 | 3.0 | 45.98 | 3.0 | 44.8 | 0.0099 """) - assert output == expected_output \ No newline at end of file + assert output == expected_output From 8257ffe25d993634768d9c9a2bcb937211807935 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 09:44:02 -0600 Subject: [PATCH 044/109] VCS:update diff and modifeid version class --- dsi/core.py | 2 +- dsi/dsi.py | 70 ++++++++++++++++++- dsi/tests/test_dsi.py | 27 ++++++- dsi/utils/version_control/__init__.py | 4 +- dsi/utils/version_control/cli.py | 23 +++--- dsi/utils/version_control/dsi_vcs.py | 51 +++++++++++--- dsi/utils/version_control/tests/test_vcs.py | 28 +++++++- dsi/utils/version_control/vcs_db.py | 2 +- .../version_control/vcs_metadata_helper.py | 53 +++++++------- 9 files changed, 208 insertions(+), 52 deletions(-) diff --git a/dsi/core.py b/dsi/core.py index 697b1ac2..fb2c9353 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1413,7 +1413,7 @@ def can_create_file_here(self, dir = "."): return False def vcs_init(self, repo_path): - """Initialize VCS repo." + """Initialize VCS repo.""" pass def vcs_status(self): diff --git a/dsi/dsi.py b/dsi/dsi.py index 057b92a1..b955606a 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -10,6 +10,8 @@ import ast import warnings + +from dsi.utils.version_control.dsi_vcs import Version warnings.filterwarnings("ignore", category=FutureWarning) logger = logging.getLogger(__name__) @@ -140,7 +142,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) msg = f"Created an instance of DSI with the {backend_name} backend: {filename}" else: msg = "Created an instance of DSI" - + self.vcs = None logger.log(logging.INFO, msg) if self.silence_messages else print(msg) def list_backends(self): @@ -1084,3 +1086,69 @@ def move(self, filepath): def fetch(self, fname): pass + + def version(self, command: str, args: str = None): + """ + Internal DSI Versioning. + + `command` : str + - init # initialize a versioning repository in a root folder + - add # add file(s) to the staging area for the next commit + - remove # remove file(s) from the staging area without touching the actual files + - delete # delete file(s) from the staging area for the next commit + - commit # commit a new version with the staged file(s) and an optional message describing the version + - log # list versions + - diff # diff between two versions. If no version is provided, diff the current version with the previous version + - restore # restore a version with commit hash + + `args` : str + - init: A required argument with the name of the root folder for the versioning repository. + - add: A required argument with the file(s) to add to the staging area for the next commit, specified as a space-separated string or list of file paths. + - remove: A required argument with the file(s) to remove from the staging area without touching the actual files, specified as a space-separated string or list of file paths. + - delete: A required argument with the file(s) to delete from the staging area for the next commit, specified as a space-separated string or list of file paths. + - commit: An optional message describing the version being committed, specified as a string. + - log: An optional argument to specify the number of recent versions to display, specified as an integer. If not provided, recent 5 versions are displayed. + - diff: An optional argument to specify the versions to compare, specified as a space-separated string or list of commit hashes. + - restore: A required argument to specify the version to restore, specified by its commit hash. + """ + print("hello from versioning") + if command == "init": + if args is None: + raise RuntimeError("version() ERROR: 'init' command requires a 'root_folder' argument specifying the name of the root folder for the versioning repository.") + + self.vcs = Version(args) + elif command == "add" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'add' command requires a 'files' argument specifying the file(s) to add to the staging area for the next commit.") + print("-->" + self.vcs.root_folder) + self.vcs.cmd_add(args.split()) + elif command == "remove" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'remove' command requires a 'files' argument specifying the file(s) to remove from the staging area for the next commit.") + self.vcs.cmd_remove(args.split()) + elif command == "delete" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'delete' command requires a 'files' argument specifying the file(s) to delete from the repository.") + self.vcs.cmd_delete(args.split()) + elif command == "commit" and self.vcs is not None: + self.vcs.cmd_commit(args) + elif command == "diff" and self.vcs is not None: + c1 = None + c2 = None + if args is not None: + arg_list = args.split() + if len(arg_list) == 1: + c1 = arg_list[0] + elif len(arg_list) == 2: + c1, c2 = arg_list + elif len(arg_list) > 2: + raise RuntimeError("version() ERROR: 'diff' command requires zero, one, or two commit hashes as arguments.") + self.vcs.cmd_diff(c1, c2) + elif command == "log" and self.vcs is not None: + self.vcs.cmd_log() + elif command == "restore" and self.vcs is not None: + if args is None: + raise RuntimeError("version() ERROR: 'restore' command requires a 'commit_hash' argument specifying the version to restore.") + self.vcs.cmd_restore(args) + else: + raise RuntimeError("version() ERROR: Invalid command or versioning repository not initialized. Please check the command and ensure 'init' has been called with a root folder argument.") \ No newline at end of file diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index cf849b5c..bf12504b 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1796,4 +1796,29 @@ def test_fail_overwrite_schema_duckdb_backend(): assert False except Exception as e: expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." - assert str(e) == expected \ No newline at end of file + assert str(e) == expected + +def main(): +# def test_versioning(): + test = DSI() + # test.version("init", os.getcwd()) + wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + # wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + test.version("add", os.path.join(wpath, "a_dummy_file")) + print(">Single file added.") + test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) + print(">Multi file added.") + print(">Versioning initialized.") + + test.version("remove", os.path.join(wpath, "schema2.json")) + print(">Single file removed.") + + test.version("commit", "Tester Commits") + test.version("log") + assert True + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/dsi/utils/version_control/__init__.py b/dsi/utils/version_control/__init__.py index 64b89704..d22f487d 100644 --- a/dsi/utils/version_control/__init__.py +++ b/dsi/utils/version_control/__init__.py @@ -1,3 +1,3 @@ -from .dsi_vcs import DSIVCS +from .dsi_vcs import Version -__all__ = ["DSIVCS"] +__all__ = ["Version"] diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py index 97823901..50b801f4 100644 --- a/dsi/utils/version_control/cli.py +++ b/dsi/utils/version_control/cli.py @@ -1,8 +1,9 @@ import argparse import datetime import sys +import os -from .dsi_vcs import DSIVCS +from .dsi_vcs import Version def main(): parser = argparse.ArgumentParser( @@ -28,8 +29,8 @@ def main(): p_log = sub.add_parser("log", help="List all commits") p_diff = sub.add_parser("diff", help="Diff two commits") - p_diff.add_argument("c1") - p_diff.add_argument("c2") + p_diff.add_argument("c1", nargs="?", default=None) + p_diff.add_argument("c2", nargs="?", default=None) p_restore = sub.add_parser("restore", help="Restore a version") p_restore.add_argument("version") @@ -39,27 +40,27 @@ def main(): # parser.print_help() # parser.format_help() if args.command == "init": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) elif args.command == "add": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_add(args.files) elif args.command == "delete": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_delete(args.files) elif args.command == "remove": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_remove(args.files) elif args.command == "commit": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_commit(args.message) elif args.command == "diff": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_diff(args.c1, args.c2) elif args.command == "log": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_log() elif args.command == "restore": - vcs = DSIVCS(os.getcwd()) + vcs = Version(os.getcwd()) vcs.cmd_restore(args.version) if __name__ == "__main__": diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index bc30538a..4c20c1a3 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -91,7 +91,7 @@ def apply_snapshot_deletes(snapshot_path: str, root_folder: str, staged_deletes: # ─────────────────────────── COMMANDS ──────────────────────────────────────── -class DSIVCS(): +class Version(): def __init__(self, folder: str): self.root_folder = os.path.abspath(folder) @@ -411,16 +411,36 @@ def cmd_diff(self, c1: str, c2: str): root_folder = os.path.abspath(self.root_folder) conn = open_db(root_folder) + def get_files_in_root_folder(): + files = {} + for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): + dirnames[:] = [d for d in dirnames if d not in {DB_NAME, SNAPSHOTS_DIR}] + for fname in filenames: + if fname not in {DB_NAME, SNAPSHOTS_DIR}: + abs_path = os.path.join(dirpath, fname) + rel_path = os.path.relpath(abs_path, root_folder) + files[rel_path] = collect_metadata(abs_path, self.root_folder) + files[rel_path]["absolute_path"] = abs_path + files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} + return files + def get_files(chash): - vid = conn.execute( - "SELECT id FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", - (root_folder, chash + "%") - ).fetchone() + vid = None + if chash == "latest": + vid = conn.execute( + "SELECT id, commit_hash FROM versions WHERE root_folder=? ORDER BY id DESC LIMIT 1", + (root_folder,) + ).fetchone() + else: + vid = conn.execute( + "SELECT id, commit_hash FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + (root_folder, chash + "%") + ).fetchone() if not vid: conn.close() sys.exit(f"Commit '{chash}' not found.") rows = conn.execute( - "SELECT relative_path, file_type, md5_hash, permissions_octal, " + "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_octal, " " owner_name, group_name, lstat " "FROM file_entries WHERE version_id=?", (vid["id"],) ).fetchall() @@ -428,12 +448,25 @@ def get_files(chash): for r in rows: rec = dict(r) # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. + snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, vid["commit_hash"][:12]) + rec["absolute_path"] = snapshot_target(snapshot_path, r["relative_path"]) rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} result[r["relative_path"]] = rec return result - files1 = get_files(c1) - files2 = get_files(c2) + files1 = files2 = {} + if c1 == None and c2 == None: + files1 = get_files_in_root_folder() + files2 = get_files("latest") + elif c1 == None: + files1 = get_files_in_root_folder() + files2 = get_files(c2) + elif c2 == None: + files1 = get_files(c1) + files2 = get_files_in_root_folder() + else: + files1 = get_files(c1) + files2 = get_files(c2) conn.close() all_paths = sorted(set(files1) | set(files2)) @@ -461,6 +494,8 @@ def get_files(chash): changes = [] if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: changes.append("content") + result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) + print(f"diff result: {result.stdout.strip()}") if f1["permissions_octal"] != f2["permissions_octal"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index c1b961ba..47a73edb 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -5,7 +5,8 @@ import pytest import stat -from dsi.utils.version_control.dsi_vcs import DSIVCS +from dsi.dsi import DSI +from dsi.utils.version_control.dsi_vcs import Version from dsi.utils.version_control.vcs_db import DB_NAME, SNAPSHOTS_DIR def require_rsync(): @@ -34,7 +35,7 @@ def latest_entries(repo_path): def test_add(tmp_path): require_rsync() - repo = DSIVCS(str(tmp_path)) + repo = Version(str(tmp_path)) empty_dir = tmp_path / "empty" nested_dir = tmp_path / "nested" / "child" @@ -68,3 +69,26 @@ def test_add(tmp_path): assert empty_entry["permissions_str"] == "rwxrwsr-x" assert eval(empty_entry["permissions_octal"]) == 0o2775 assert empty_entry["setgid"] == 1 + + +def test_versioning(): + test = DSI() + # test.version("init", os.getcwd()) + # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + test.version("add", os.path.join(wpath, "a_dummy_file")) + print(">Single file added.") + test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) + print(">Multi file added.") + print(">Versioning initialized.") + + test.version("remove", os.path.join(wpath, "schema2.json")) + print(">Single file removed.") + + test.version("commit", "Tester Commits") + test.version("log") + test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") + assert True diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index aaf21fca..c5e849fa 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS staging ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, - absolute_path TEXT NOT NULL, + absolute_path TEXT NOT NULL UNIQUE, -- absolute path must be unique in staging action TEXT NOT NULL DEFAULT 'add', added_at TEXT NOT NULL, -- ISO-8601 timestamp UNIQUE(root_folder, absolute_path) diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index 9b756f3b..ec39915c 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -76,12 +76,14 @@ def get_acl(path: str) -> Optional[str]: acl_entries.append(acl_part) return ";".join(acl_entries) if acl_entries else None - else: + elif sys.platform == "linux": result = subprocess.run( ["getfacl", "--omit-header", "--absolute-names", path], capture_output=True, text=True, timeout=5 ) return result.stdout.strip() or None + else: + return None except (FileNotFoundError, subprocess.TimeoutExpired): return None @@ -107,29 +109,30 @@ def set_acl(path: str, acl_string: str) -> Optional[str]: def get_xattrs(path: str) -> Optional[str]: - """Return JSON dict of extended attributes, or None.""" - try: - import xattr # pyxattr - attrs = {} - for key in xattr.listxattr(path, symlink=True): - try: - val = xattr.getxattr(path, key, symlink=True) - attrs[key] = val.decode("utf-8", errors="replace") - except OSError: - attrs[key] = "" - return json.dumps(attrs) if attrs else None - except ImportError: - # Fallback: use getfattr - try: - result = subprocess.run( - ["getfattr", "-d", "-m", "-", "--absolute-names", path], - capture_output=True, text=True, timeout=5 - ) - return result.stdout.strip() or None - except FileNotFoundError: - return None - except OSError: - return None + return None + # """Return JSON dict of extended attributes, or None.""" + # try: + # import xattr # pyxattr + # attrs = {} + # for key in xattr.listxattr(path, symlink=True): + # try: + # val = xattr.getxattr(path, key, symlink=True) + # attrs[key] = val.decode("utf-8", errors="replace") + # except OSError: + # attrs[key] = "" + # return json.dumps(attrs) if attrs else None + # except ImportError: + # # Fallback: use getfattr + # try: + # result = subprocess.run( + # ["getfattr", "-d", "-m", "-", "--absolute-names", path], + # capture_output=True, text=True, timeout=5 + # ) + # return result.stdout.strip() or None + # except FileNotFoundError: + # return None + # except OSError: + # return None def get_security_context(path: str) -> Optional[str]: @@ -218,7 +221,7 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: # external / subprocess-derived attributes "acl_text": get_acl(abs_path), - "xattrs": get_xattrs(abs_path), + # "xattrs": get_xattrs(abs_path), "security_context": get_security_context(abs_path), "symlink_target": os.readlink(abs_path) if ftype == "symlink" else None, From f7b3e7770325494ebca296bf95fd6b6f3b7b1f41 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 10:52:13 -0600 Subject: [PATCH 045/109] VCS: update versioning tests --- dsi/tests/test_dsi.py | 13 +++++++------ dsi/utils/version_control/tests/test_vcs.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index bf12504b..671d0397 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1798,12 +1798,12 @@ def test_fail_overwrite_schema_duckdb_backend(): expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." assert str(e) == expected -def main(): -# def test_versioning(): +# def main(): +def test_versioning(): test = DSI() # test.version("init", os.getcwd()) - wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - # wpath = os.getcwd() + # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" + wpath = os.getcwd() test.version("init", wpath) assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") @@ -1818,7 +1818,8 @@ def main(): test.version("commit", "Tester Commits") test.version("log") + test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") assert True -if __name__ == "__main__": - main() \ No newline at end of file +# if __name__ == "__main__": +# main() \ No newline at end of file diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 47a73edb..6b7ba913 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -90,5 +90,5 @@ def test_versioning(): test.version("commit", "Tester Commits") test.version("log") - test.version("diff", "77345f1115d94c69a1255b9fb0524378 4af9e3d4dc854d699b96b5a84f913ac0") + test.version("diff") assert True From 900fa768586c0d33dc1259008afc2829f43d537e Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 29 Apr 2026 11:44:34 -0600 Subject: [PATCH 046/109] VCS: add readme file --- dsi/utils/version_control/README.md | 335 ++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 dsi/utils/version_control/README.md diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md new file mode 100644 index 00000000..8b5dd0ea --- /dev/null +++ b/dsi/utils/version_control/README.md @@ -0,0 +1,335 @@ +# dsi-vcs: rsync-based File Version Control with Full Linux Metadata + +## Overview + +**dsi-vcs** is a lightweight file version control system designed for capturing and preserving complete Linux file metadata. Unlike traditional VCS tools, dsi-vcs focuses on: + +- **Full metadata capture**: permissions, ownership, ACLs, extended attributes, and SELinux contexts +- **Rsync-based snapshots**: efficient storage with hard-link deduplication +- **SQLite database**: structured metadata storage for querying and diffing +- **Complete file history**: MD5 hashes, file stats, and all metadata changes tracked + +--- + +## CLI Commands + +Initialize dsi-vcs in the current directory: + +```bash +dsi-vcs init +``` + +### Stage Files for Commit + +**Add files/directories:** + +```bash +dsi-vcs add [ ...] +dsi-vcs add ./data +dsi-vcs add file1.txt file2.txt +``` + +**Stage files for deletion:** + +```bash +dsi-vcs delete [ ...] +dsi-vcs delete ./old_data/ +``` + +**Remove files from staging (without deleting):** + +```bash +dsi-vcs remove [ ...] +dsi-vcs remove file1.txt +``` + +### Commit Changes + +Create a snapshot with an optional message: + +```bash +dsi-vcs commit +dsi-vcs commit "Initial data import" +``` + +### View History + +List all commits: + +```bash +dsi-vcs log +``` + +Example output: + +```shell +COMMIT HASH OWNER DATE/TIME (UTC) FILES BYTES MESSAGE +──────────────────────────────────────────────────────────────────────────────────────────────────── +f826177ae78f4e48a8c08054e2bb9a71 owner1 2026-04-28T21:10:05.339321+00:00 7 15,557 first commit +4af9e3d4dc854d699b96b5a84f913ac0 owner2 2026-04-28T21:19:47.167081+00:00 7 15,559 second commit +``` + +### Compare Versions + +Diff two commits (shows added, modified, deleted files). + +Parameters: + +- Provide upto two commit hash as paramerter. +- No parameter will compare with the latest changes with the last commit. +- One parameter will compare with the latest change with the provided commit version. +- Two parameters will compare between the provided versions. + +```bash +dsi-vcs diff +dsi-vcs diff f826177ae78f4e48a8c08054e2bb9a71 +``` + +Example output: + +```shell +Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) + +STATUS PATH +────────────────────────────────────────────────────────────────────── +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + +Summary: +0 added -0 deleted ~3 modified =4 unchanged +``` + +### Restore a Version + +Restore the entire repository to a previous commit: + +```bash +dsi-vcs restore +dsi-vcs restore abc123def456 +``` + +## Python API + +Integrated within DSI. User versioning with the function `version(command, args)` + +### Usage + +```python +obj.version(command: str, args: str = None) +``` + +### Parameters + +| Parameter | Type | Description | +| ----------- | ------- | -------------------------------------------------------- | +| `command` | `str` | The versioning operation to perform (see commands below) | +| `args` | `str` | Optional or required arguments depending on the command | + +--- + +### `init` + +Initializes a versioning repository in a root folder. + +```python +obj.version("init", "my_project_folder") +``` + +**Args (required):** Name of the root folder for the versioning repository. + +--- + +### `add` + +Adds one or more files to the staging area for the next commit. + +```python +obj.version("add", "file1.py file2.py") +``` + +**Args (required):** Space-separated file paths to stage. + +--- + +### `remove` + +Removes one or more files from the staging area **without** deleting the actual files. + +```python +obj.version("remove", "file1.py") +``` + +**Args (required):** Space-separated file paths to unstage. + +--- + +### `delete` + +Marks one or more files for deletion in the next commit. + +```python +obj.version("delete", "file1.py file2.py") +``` + +**Args (required):** Space-separated file paths to delete. + +--- + +### `commit` + +Commits all staged changes as a new version snapshot. + +```python +obj.version("commit", "Initial release") # with message +obj.version("commit") # without message +``` + +**Args (optional):** A descriptive message for the version being committed. + +--- + +### `log` + +Lists recent committed versions. + +```python +obj.version("log") # shows last 5 versions (default) +obj.version("log", "10") # shows last 10 versions +``` + +**Args (optional):** Number of recent versions to display. Defaults to `5`. + +--- + +### `diff` + +Shows the differences between two versions. + +```python +obj.version("diff") # current vs previous +obj.version("diff", "abc123") # specific commit vs its previous +obj.version("diff", "abc123 def456") # between two specific commits +``` + +**Args (optional):** Zero, one, or two commit hashes separated by a space. + +| # of hashes | Behavior | +| ----------- | -------------------------------------------------- | +| 0 | Diffs current version against previous | +| 1 | Diffs specified commit against its previous | +| 2 | Diffs the two specified commits against each other | + +--- + +### `restore` + +Restores the repository to a previously committed version. + +```python +obj.version("restore", "abc123def456") +``` + +**Args (required):** The commit hash of the version to restore. + +Example : Basic Workflow + +```python +from dsi.utils.version_control import Version + +# Initialize dsi +dsi = DSI() + +# Initialize repo +dsi.version("init", "/data/archive") + +# Stage files +dsi.version("add", ["./documents", "config.json"]) + +# Commit +dsi.version("commit", "Initial archive") + +# View history +dsi.version("log") + +# Modify files and commit again +dsi.version(["./documents"]) +dsi.version("commit", "Updated documents") + +# Compare two versions +dsi.version("diff") +``` + +## Database Schema + +### `versions` Table + +| Column | Type | Description | +| ------------- | ---------- | --------------------------- | +| id | INTEGER PK | Auto-increment ID | +| root_folder | TEXT | Repository root path | +| commit_hash | TEXT | UUID4 hex (32 chars) | +| committed_at | TEXT | ISO-8601 timestamp | +| owner_name | TEXT | Linux user who committed | +| message | TEXT | Optional commit message | +| snapshot_path | TEXT | Path to rsync snapshot copy | +| file_count | INTEGER | Number of files in commit | +| total_bytes | INTEGER | Total size in bytes | + +### `file_entries` Table + +Stores metadata for each file in each commit. + +| Column | Type | Description | +| ---------------------- | ---------- | -------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| absolute_path | TEXT | Full path | +| file_name | TEXT | Filename only | +| file_type | TEXT | file/dir/symlink/etc | +| md5_hash | TEXT | Content hash (files only) | +| lstat | TEXT | JSON of os.lstat() result | +| permissions_octal | TEXT | e.g., "0o755" | +| permissions_str | TEXT | e.g., "rwxr-xr-x" | +| owner_name | TEXT | Username | +| group_name | TEXT | Group name | +| setuid, setgid, sticky | INTEGER | Special bits (0/1) | +| acl_text | TEXT | Raw getfacl output | +| xattrs | TEXT | JSON dict of extended attributes | +| security_context | TEXT | SELinux context | +| symlink_target | TEXT | Target of symlink | + +### `staging` Table + +Temporary storage for files to be committed. + +| Column | Type | Description | +| ------------- | ---------- | ----------------------- | +| id | INTEGER PK | Auto-increment | +| root_folder | TEXT | Partition key | +| absolute_path | TEXT | Full file path (UNIQUE) | +| action | TEXT | "add" or "delete" | +| added_at | TEXT | ISO-8601 timestamp | + +--- + +## Directory Structure + +``` +root_folder/ +└── .dsi_vcs_snapshots/ # rsync snapshot directory + ├── .dsi_vcs.db # SQLite metadata database + ├── abc123def456/ # Snapshot for commit abc123def456 + ├── xyz789abc123/ # Snapshot for commit xyz789abc123 + └── ... +``` From d77cd548f2ea2dae3f97371fbdf2c1ccd2711d75 Mon Sep 17 00:00:00 2001 From: krhom Date: Wed, 15 Apr 2026 18:22:11 -0600 Subject: [PATCH 047/109] changes to query --- dsi/backends/ndp.py | 14 ++++++++------ examples/ndp/ndp_developer/3.query.py | 14 +++++++++++--- examples/ndp/ndp_user/3.query.py | 26 +++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 5827062f..efdb58a9 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -49,17 +49,19 @@ class NDP(Webserver): # ---------------------------- # Initialization # ---------------------------- - def __init__(self, url=None, **kwargs): + def __init__(self, url=None, params=None, **kwargs): """ Initialize backend and optionally load data from CKAN API. url : Base CKAN URL. If None, a default CKAN endpoint is used. - kwargs : + # TODO: Incorporate back in api_key : optional API key verify_ssl : toggle SSL verification (default False) - webargs : initial query params + params : initial query params + + kwargs : """ DEFAULT_URL = "https://nationaldataplatform.org/catalog" @@ -68,7 +70,7 @@ def __init__(self, url=None, **kwargs): api_key = kwargs.get("api_key") verify_ssl = kwargs.get("verify_ssl", False) - webargs = kwargs.get("webargs") + webargs = params parsed = urlparse(base_url) if not parsed.scheme or not parsed.netloc: @@ -221,7 +223,7 @@ def _rows_to_table(self, rows): # --------------------------------------------------- # Query Interface (in-memory) # --------------------------------------------------- - def query_artifacts(self, query, **kwargs): + def query_artifacts(self, query, table_name, dict_return, **kwargs): """ Returns filtered rows from cached tables using pandas.query(). @@ -240,7 +242,7 @@ def query_artifacts(self, query, **kwargs): Filtered results from selected in-memory table. """ - queryargs = kwargs or {} + queryargs = queryargs or {} if not self._loaded: return OrderedDict() diff --git a/examples/ndp/ndp_developer/3.query.py b/examples/ndp/ndp_developer/3.query.py index 7818832e..892198ee 100644 --- a/examples/ndp/ndp_developer/3.query.py +++ b/examples/ndp/ndp_developer/3.query.py @@ -4,16 +4,24 @@ def main(verbose=False): t = Terminal() - t.load_module("backend", "NDP", "back-read", webargs={"keywords": "energy", "limit": 5}) + + # Called by ndp __init__ + t.load_module("backend", "NDP", "back-read", webargs={"keywords": "energy", "limit": 10}) + + # Called by ndp's query_artifact result = t.artifact_handler(interaction_type='query', query="`num_resources` > 10", queryargs={"table": "datasets", "dict_return": True}) # This is a filesystem example #data = terminal_query.artifact_handler(interaction_type='query', query = "SELECT * FROM input;") if verbose: - print("Query results (num_resources > 10):") + print("Query results (num_resources > 10):" + str(len(result))) + for table_name, table_data in result.items(): - print(table_name, list(table_data)) + print(f"\n{table_name}") + + for col, values in table_data.items(): + print(f"{col}: {values}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="NDP query example") diff --git a/examples/ndp/ndp_user/3.query.py b/examples/ndp/ndp_user/3.query.py index 64876eb7..0ace02a9 100644 --- a/examples/ndp/ndp_user/3.query.py +++ b/examples/ndp/ndp_user/3.query.py @@ -23,4 +23,28 @@ def main(verbose=False): parser = argparse.ArgumentParser(description="NDP query example") parser.add_argument("--verbose", action="store_true", help="Show detailed output") args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file + main(verbose=args.verbose) + +# from dsi.core import DSI + +# # 1) Initialize DSI with the NDP backend +# dsi = DSI(backend_name="NDP") + +# # 2) Load a dataset into the backend (example: CSV via standard reader) +# dsi.read( +# data_sources="traffic_data.csv", +# reader_name="CSV", +# table_name="traffic" +# ) + +# # 3) Run a SQL query on the NDP-backed dataset +# result = dsi.query(""" +# SELECT road, +# AVG(speed) AS avg_speed, +# COUNT(*) AS observations +# FROM traffic +# GROUP BY road +# ORDER BY avg_speed DESC +# """, collection=True) + +# print(result) \ No newline at end of file From b0dccd02016312e785ea6e316e174d9a46de3e4b Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 00:10:25 -0600 Subject: [PATCH 048/109] refactor: changed init, query, process, and load to use two tier data table --- dsi/backends/ndp.py | 149 ++++++++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 62 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index efdb58a9..f2b3855f 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -56,85 +56,101 @@ def __init__(self, url=None, params=None, **kwargs): url : Base CKAN URL. If None, a default CKAN endpoint is used. - # TODO: Incorporate back in + params : + Dictionary of initial query parameters used to fetch data from CKAN. + Supported keys: + - keywords + - organization + - tags + - formats + - limit + + kwargs : api_key : optional API key verify_ssl : toggle SSL verification (default False) - params : initial query params - - kwargs : """ DEFAULT_URL = "https://nationaldataplatform.org/catalog" base_url = url or DEFAULT_URL - api_key = kwargs.get("api_key") - verify_ssl = kwargs.get("verify_ssl", False) - webargs = params + # ---------------------------- + # Auth / connection config + # ---------------------------- + self.api_key = kwargs.get("api_key") + self.verify_ssl = kwargs.get("verify_ssl", False) parsed = urlparse(base_url) if not parsed.scheme or not parsed.netloc: raise ValueError("Invalid base_url") self.base_url = base_url.rstrip("/") - self.api_key = api_key - self.verify_ssl = verify_ssl self.headers = {} - if api_key: - self.headers["Authorization"] = api_key + if self.api_key: + self.headers["Authorization"] = self.api_key - self._cache = OrderedDict({ - "datasets": OrderedDict(), - "resources": OrderedDict() - }) + # Data storage (tiered structure) + # Tier 1: datasets + # Tier 2: per-dataset resource tables + self._cache = OrderedDict() + self._resource_tables = [] self._loaded = False + self.params = params or {} - if webargs: - self._load_initial_data(webargs) + # Initial data load + if self.params: + self._load_initial_data(self.params) # --------------------------------------------------- # Initial Data Load # --------------------------------------------------- - def _load_initial_data(self, kwargs): + def _load_initial_data(self, params): """ - Fetch datasets/resources from CKAN API and store in memory + Fetch datasets/resources from CKAN API and store in memory. - kwargs: + params: keywords, organization, tags, formats, limit """ - params = {"rows": kwargs.get("limit", 100)} + query_params = {"rows": params.get("limit", 100)} q_parts, fq_parts = [], [] - if kwargs.get("keywords"): - q_parts.append(kwargs["keywords"]) + if params.get("keywords"): + q_parts.append(params["keywords"]) - if kwargs.get("organization"): - fq_parts.append(f"organization:{kwargs['organization']}") + if params.get("organization"): + fq_parts.append(f"organization:{params['organization']}") - if kwargs.get("tags"): - fq_parts += [f"tags:{t}" for t in kwargs["tags"]] + if params.get("tags"): + fq_parts += [f"tags:{t}" for t in params["tags"]] - if kwargs.get("formats"): + if params.get("formats"): fq_parts.append("(" + " OR ".join( - [f"res_format:{f}" for f in kwargs["formats"]]) + ")") + [f"res_format:{f}" for f in params["formats"]]) + ")") if q_parts: - params["q"] = " ".join(q_parts) + query_params["q"] = " ".join(q_parts) if fq_parts: - params["fq"] = " AND ".join(fq_parts) + query_params["fq"] = " AND ".join(fq_parts) + + result = self._request("package_search", query_params) - result = self._request("package_search", params) + dataset_rows, resource_map = self._extract_tables(result.get("results", [])) - ds_rows, rs_rows = self._extract_tables(result.get("results", [])) + # Tier 1: datasets + self._cache["datasets"] = self._rows_to_table(dataset_rows) - self._cache["datasets"] = self._rows_to_table(ds_rows) - self._cache["resources"] = self._rows_to_table(rs_rows) + # Tier 2: per-dataset resource tables + self._resource_tables = [] + for dataset_name, rows in resource_map.items(): + table_name = f"resources_{dataset_name}" + self._cache[table_name] = self._rows_to_table(rows) + self._resource_tables.append(table_name) self._loaded = True @@ -223,46 +239,55 @@ def _rows_to_table(self, rows): # --------------------------------------------------- # Query Interface (in-memory) # --------------------------------------------------- - def query_artifacts(self, query, table_name, dict_return, **kwargs): + def query_artifacts(self, query, dict_return=True, **kwargs): """ - Returns filtered rows from cached tables using pandas.query(). + Query all tables using a pandas query string. - query : - Pandas query string used to filter table rows. + dict_return : + If True, returns dict format (default). + If False, returns pandas DataFrames. + """ - queryargs (kwargs): - table : - datasets | resources (default: datasets) + if not self._loaded: + return {} - dict_return : - If True, returns dict format. - If False, returns pandas DataFrame. + results = {} - `return` : - Filtered results from selected in-memory table. - """ + for t_name, table in self._cache.items(): + df = pd.DataFrame(table) - queryargs = queryargs or {} + if df.empty: + continue - if not self._loaded: - return OrderedDict() + try: + result_df = df.query(query, engine="python") + + if not result_df.empty: + results[t_name] = ( + result_df.to_dict(orient="list") + if dict_return else result_df + ) - table_name = queryargs.get("table", "datasets") - dict_return = queryargs.get("dict_return", True) + except Exception as e: + raise ValueError(f"Query error in {t_name}: {e}") - df = pd.DataFrame(self._cache.get(table_name, {})) + return results - if df.empty: - return {table_name: {}} if dict_return else pd.DataFrame() - try: - result_df = df.query(query, engine="python") - result = result_df.to_dict(orient="list") + # --------------------------------------------------- + # + # --------------------------------------------------- + def process_artifacts(self): + """ + Returns all tables (datasets + per-dataset resource tables) - return {table_name: result} if dict_return else result_df + Useful for writing/exporting. + """ + + if not self._loaded: + return {} - except Exception as e: - raise ValueError(f"CKAN query_artifacts error at {table_name}: {e}") + return self._cache # --------------------------------------------------- From 1d6bb3e2ed02ae138542bc4fd6b40c68c9c2405f Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 00:56:17 -0600 Subject: [PATCH 049/109] refactor: updated ndp for table change --- dsi/backends/ndp.py | 188 +++++++++++++++++++++++++------------------- 1 file changed, 105 insertions(+), 83 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index f2b3855f..f6d8bfa3 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -111,7 +111,7 @@ def _load_initial_data(self, params): """ Fetch datasets/resources from CKAN API and store in memory. - params: + `params`: keywords, organization, tags, formats, limit """ @@ -148,7 +148,7 @@ def _load_initial_data(self, params): # Tier 2: per-dataset resource tables self._resource_tables = [] for dataset_name, rows in resource_map.items(): - table_name = f"resources_{dataset_name}" + table_name = dataset_name self._cache[table_name] = self._rows_to_table(rows) self._resource_tables.append(table_name) @@ -183,16 +183,20 @@ def _request(self, endpoint, params=None): def _extract_tables(self, datasets): """ - Flatten CKAN dataset JSON into row format. + Flatten CKAN dataset JSON into: + - dataset_rows (list of dicts) + - resource_map (dict: dataset_name -> list of resource rows) """ dataset_rows = [] - resource_rows = [] + resource_map = {} for ds in datasets: + dataset_name = ds.get("name") or ds.get("id") + dataset_rows.append({ "id": ds.get("id"), - "name": ds.get("name"), + "name": dataset_name, "title": ds.get("title"), "notes": ds.get("notes"), "organization": (ds.get("organization") or {}).get("title"), @@ -204,8 +208,10 @@ def _extract_tables(self, datasets): "num_resources": ds.get("num_resources", 0) }) + resource_map[dataset_name] = [] + for r in ds.get("resources", []): - resource_rows.append({ + resource_map[dataset_name].append({ "resource_id": r.get("id"), "resource_name": r.get("name"), "format": r.get("format"), @@ -215,7 +221,7 @@ def _extract_tables(self, datasets): "dataset_title": ds.get("title") }) - return dataset_rows, resource_rows + return dataset_rows, resource_map def _rows_to_table(self, rows): @@ -243,7 +249,7 @@ def query_artifacts(self, query, dict_return=True, **kwargs): """ Query all tables using a pandas query string. - dict_return : + `dict_return` : If True, returns dict format (default). If False, returns pandas DataFrames. """ @@ -275,13 +281,20 @@ def query_artifacts(self, query, dict_return=True, **kwargs): # --------------------------------------------------- - # + # Artifact Processing (tiered table construction) # --------------------------------------------------- def process_artifacts(self): """ - Returns all tables (datasets + per-dataset resource tables) + Returns all cached tables in tiered format. + + Structure: + { + "datasets": , + "": , + ... + } - Useful for writing/exporting. + Useful for exporting or writing to external systems. """ if not self._loaded: @@ -295,45 +308,43 @@ def process_artifacts(self): # --------------------------------------------------- def validate_urls(self): """ - Validates resource URLs and updates cache in-place. - - Adds: - - 'url_valid' column to the resources table indicating URL reachability. - - `return` : None + Validates resource URLs across all resource tables. + Adds 'url_valid' column. """ - resources = self._cache["resources"] - urls = resources.get("url", []) - - valid_list = [] headers = {"User-Agent": "NDP-Validator"} - for url in urls: - try: - r = requests.head( - url, - allow_redirects=True, - headers=headers, - timeout=10, - verify=self.verify_ssl - ) - - if r.status_code == 405: - r = requests.get( + for table_name in self._resource_tables: + table = self._cache.get(table_name, {}) + urls = table.get("url", []) + + valid_list = [] + + for url in urls: + try: + r = requests.head( url, - stream=True, + allow_redirects=True, headers=headers, timeout=10, verify=self.verify_ssl ) - valid_list.append(200 <= r.status_code < 400) + if r.status_code == 405: + r = requests.get( + url, + stream=True, + headers=headers, + timeout=10, + verify=self.verify_ssl + ) + + valid_list.append(200 <= r.status_code < 400) - except Exception: - valid_list.append(False) + except Exception: + valid_list.append(False) - resources["url_valid"] = valid_list + table["url_valid"] = valid_list # --------------------------------------------------- @@ -522,72 +533,87 @@ def find_relation(self, column_name, relation, **kwargs): """ return [] + # --------------------------------------------------- # Utility / Display # --------------------------------------------------- - def list(self, **kwargs): + def list(self, collection=False): """ - Lists all available tables in the backend. + Lists tables or prints metadata. - `return` : list of str - A list of table names currently stored in memory. + collection : bool, default False + True → return list of table names + False → print table names + dimensions """ - return list(self._cache.keys()) + + if collection: + return list(self._cache.keys()) + + for name, table in self._cache.items(): + df = pd.DataFrame(table) + print(f"{name}: ({len(df)} rows, {len(df.columns)} cols)") - def summary(self, table_name, **kwargs): + def summary(self, table_name=None): """ - Returns summary information about tables in the backend. - - table_name : - Optional table name. If provided, returns summary only for that table. + Returns numerical metadata for tables. - return : - dict containing: - - loaded: bool - - tables: dict mapping table names to row counts + `table_name` : str, optional + If provided → returns a single DataFrame + If None → returns list of DataFrames """ - tables_summary = {} + summaries = [] for name, table in self._cache.items(): - - if table_name is not None and name != table_name: + if table_name and name != table_name: continue - row_count = len(next(iter(table.values()), [])) if table else 0 - tables_summary[name] = row_count + df = pd.DataFrame(table) - return { - "loaded": self._loaded, - "tables": tables_summary - } - + summary_df = pd.DataFrame([{ + "table_name": name, + "num_rows": len(df), + "num_columns": len(df.columns), + "columns": list(df.columns) + }]) + + summaries.append(summary_df) + + if table_name: + return summaries[0] if summaries else pd.DataFrame() - def display(self, table_name=None, **kwargs): + return summaries + + + def display(self, table_name, num_rows=25, display_cols=None): """ - Displays a preview of table data. + Displays rows from a specified table. - table_name : - Optional table name. If provided, displays only that table. + `table_name` : str + Name of the table. - kwargs : - limit : number of rows to display per table (default 10) + `num_rows` : int, default 25 + Number of rows to display. + + `display_cols` : list of str, optional + Subset of columns to display. - return : - None (prints DataFrame previews) + `return` : + pandas.DataFrame """ - limit = kwargs.get("limit", 10) + table = self._cache.get(table_name) - for name, table in self._cache.items(): + if not table: + raise ValueError(f"Table '{table_name}' not found") - if table_name is not None and name != table_name: - continue + df = pd.DataFrame(table) - print(f"\n{name}:") - df = pd.DataFrame(table) - print(df.head(limit)) + if display_cols: + df = df[display_cols] + + return print(df.head(num_rows)) def notebook(self, **kwargs): @@ -605,14 +631,10 @@ def notebook(self, **kwargs): def close(self): """ Resets backend state and clears all cached data. - - `return` : None """ - self._cache = OrderedDict({ - "datasets": OrderedDict(), - "resources": OrderedDict() - }) + self._cache = OrderedDict() + self._resource_tables = [] self._loaded = False From a0af2768e0a8fdcfcd01f73eca9672e94901678d Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 01:06:17 -0600 Subject: [PATCH 050/109] refactor: minor issue fix --- dsi/backends/ndp.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index f6d8bfa3..c2c24fec 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -148,7 +148,7 @@ def _load_initial_data(self, params): # Tier 2: per-dataset resource tables self._resource_tables = [] for dataset_name, rows in resource_map.items(): - table_name = dataset_name + table_name = f"resources_{dataset_name}" self._cache[table_name] = self._rows_to_table(rows) self._resource_tables.append(table_name) @@ -208,7 +208,7 @@ def _extract_tables(self, datasets): "num_resources": ds.get("num_resources", 0) }) - resource_map[dataset_name] = [] + resource_map.setdefault(dataset_name, []) for r in ds.get("resources", []): resource_map[dataset_name].append({ @@ -547,7 +547,10 @@ def list(self, collection=False): """ if collection: - return list(self._cache.keys()) + return { + "datasets": ["datasets"], + "resources": self._resource_tables + } for name, table in self._cache.items(): df = pd.DataFrame(table) @@ -559,31 +562,30 @@ def summary(self, table_name=None): Returns numerical metadata for tables. `table_name` : str, optional - If provided → returns a single DataFrame - If None → returns list of DataFrames + If provided → returns summary for a single table + If None → returns summary for all tables as one DataFrame """ + if not self._loaded: + return pd.DataFrame() + summaries = [] for name, table in self._cache.items(): + if table_name and name != table_name: continue df = pd.DataFrame(table) - summary_df = pd.DataFrame([{ + summaries.append({ "table_name": name, "num_rows": len(df), "num_columns": len(df.columns), "columns": list(df.columns) - }]) - - summaries.append(summary_df) - - if table_name: - return summaries[0] if summaries else pd.DataFrame() + }) - return summaries + return pd.DataFrame(summaries) def display(self, table_name, num_rows=25, display_cols=None): @@ -613,7 +615,9 @@ def display(self, table_name, num_rows=25, display_cols=None): if display_cols: df = df[display_cols] - return print(df.head(num_rows)) + result = df.head(num_rows) + print(result) + return result def notebook(self, **kwargs): From 3850d5c4b8a9709467b33a4a17b58cff794612c0 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 02:21:15 -0600 Subject: [PATCH 051/109] refactor: upgraded tests --- examples/ndp/ndp_developer/1.ingest.py | 20 +++++++------- examples/ndp/ndp_developer/2.process.py | 30 ++++++++++++--------- examples/ndp/ndp_developer/3.query.py | 36 +++++++++++++++---------- examples/ndp/ndp_developer/5.inspect.py | 3 ++- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/examples/ndp/ndp_developer/1.ingest.py b/examples/ndp/ndp_developer/1.ingest.py index b6098505..95cad4e4 100644 --- a/examples/ndp/ndp_developer/1.ingest.py +++ b/examples/ndp/ndp_developer/1.ingest.py @@ -1,21 +1,21 @@ -# examples/ndp/ndp_developer/1.ingest.py -import argparse from dsi.core import Terminal def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] + terminal = Terminal() - backend.query_artifacts(query=None, kwargs={"keywords": "data", "limit": 5}) + # Load NDP backend (read-only) + terminal.load_module("backend", "NDP", "back-read", params={"keywords": "data", "limit": 5}) + + backend = terminal.active_modules["back-read"][0] if verbose: + artifacts = backend.summary() print("Datasets loaded:") - for table_name, table_data in backend.process_artifacts().items(): - print(table_name, list(table_data.keys())) + print(artifacts) if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP ingest example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/2.process.py b/examples/ndp/ndp_developer/2.process.py index 748e77e6..c3ea8234 100644 --- a/examples/ndp/ndp_developer/2.process.py +++ b/examples/ndp/ndp_developer/2.process.py @@ -1,15 +1,15 @@ -# examples/ndp/ndp_developer/2.process.py import argparse from dsi.core import Terminal def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] + terminal = Terminal() - backend.query_artifacts( - query=None, - kwargs={ + # 1. Load backend + trigger initial CKAN fetch (INGEST STEP) + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ "keywords": "climate", "tags": ["temperature", "humidity"], "organization": "NASA", @@ -18,16 +18,22 @@ def main(verbose=False): } ) + backend = terminal.active_modules["back-read"][0] + + # 3. Process and print out data tables = backend.process_artifacts() + if verbose: - print("Processed artifacts preview:") + print("\nProcessed artifacts:\n") + for table_name, table_data in tables.items(): - print(table_name) - for col in table_data: - print(f" {col}: {table_data[col][:3]}") # first 3 rows + print(f"\n[{table_name}]") + + for col, values in table_data.items(): + print(f" {col}: {values[:3]}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="NDP process example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") + parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/3.query.py b/examples/ndp/ndp_developer/3.query.py index 892198ee..5f45c21f 100644 --- a/examples/ndp/ndp_developer/3.query.py +++ b/examples/ndp/ndp_developer/3.query.py @@ -1,30 +1,38 @@ -# examples/ndp/ndp_developer/3.query.py import argparse from dsi.core import Terminal def main(verbose=False): - t = Terminal() - - # Called by ndp __init__ - t.load_module("backend", "NDP", "back-read", webargs={"keywords": "energy", "limit": 10}) - - # Called by ndp's query_artifact - result = t.artifact_handler(interaction_type='query', query="`num_resources` > 10", queryargs={"table": "datasets", "dict_return": True}) + terminal = Terminal() - # This is a filesystem example - #data = terminal_query.artifact_handler(interaction_type='query', query = "SELECT * FROM input;") + # 1. Load NDP backend (ingest step) + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "energy", "limit": 10} + ) + backend = terminal.active_modules["back-read"][0] + + # 2. Query step + result = backend.query_artifacts( + query="`num_resources` > 10", + dict_return=True + ) + + # 3. OUTPUT STEP if verbose: - print("Query results (num_resources > 10):" + str(len(result))) + print("\nQuery results (num_resources > 10):") + print(f"Tables returned: {len(result)}") for table_name, table_data in result.items(): - print(f"\n{table_name}") + print(f"\n[{table_name}]") for col, values in table_data.items(): - print(f"{col}: {values}") + print(f" {col}: {values}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="NDP query example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") + parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/5.inspect.py b/examples/ndp/ndp_developer/5.inspect.py index c32659fb..717a3a08 100644 --- a/examples/ndp/ndp_developer/5.inspect.py +++ b/examples/ndp/ndp_developer/5.inspect.py @@ -22,4 +22,5 @@ def main(verbose=False): parser = argparse.ArgumentParser(description="NDP inspect example") parser.add_argument("--verbose", action="store_true", help="Show detailed output") args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file + main(verbose=args.verbose) + \ No newline at end of file From 101d3452e134012f601fd125b5a35bf4a9d6ad8f Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 09:54:57 -0600 Subject: [PATCH 052/109] refactor: updated tests for ndp dev --- dsi/backends/ndp.py | 3 ++ .../{1.ingest.py => 1.load_basic.py} | 3 +- examples/ndp/ndp_developer/2.list_summary.py | 35 +++++++++++++ examples/ndp/ndp_developer/2.process.py | 39 -------------- examples/ndp/ndp_developer/3.display.py | 35 +++++++++++++ examples/ndp/ndp_developer/4.find.py | 25 --------- .../ndp_developer/{3.query.py => 4.query.py} | 1 + examples/ndp/ndp_developer/5.find.py | 41 +++++++++++++++ examples/ndp/ndp_developer/5.inspect.py | 26 ---------- examples/ndp/ndp_developer/6.process.py | 32 ++++++++++++ examples/ndp/ndp_developer/6.validate.py | 35 ------------- examples/ndp/ndp_developer/7.notebook.py | 19 ------- examples/ndp/ndp_developer/7.validate.py | 51 +++++++++++++++++++ examples/ndp/ndp_developer/8.close.py | 28 +++++++--- examples/ndp/ndp_developer/9.run_all.py | 14 ++--- 15 files changed, 229 insertions(+), 158 deletions(-) rename examples/ndp/ndp_developer/{1.ingest.py => 1.load_basic.py} (86%) create mode 100644 examples/ndp/ndp_developer/2.list_summary.py delete mode 100644 examples/ndp/ndp_developer/2.process.py create mode 100644 examples/ndp/ndp_developer/3.display.py delete mode 100644 examples/ndp/ndp_developer/4.find.py rename examples/ndp/ndp_developer/{3.query.py => 4.query.py} (96%) create mode 100644 examples/ndp/ndp_developer/5.find.py delete mode 100644 examples/ndp/ndp_developer/5.inspect.py create mode 100644 examples/ndp/ndp_developer/6.process.py delete mode 100644 examples/ndp/ndp_developer/6.validate.py delete mode 100644 examples/ndp/ndp_developer/7.notebook.py create mode 100644 examples/ndp/ndp_developer/7.validate.py diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index c2c24fec..6fd39e83 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -274,6 +274,9 @@ def query_artifacts(self, query, dict_return=True, **kwargs): if dict_return else result_df ) + except pd.errors.UndefinedVariableError: + # Skip tables that don't have the queried columns + continue except Exception as e: raise ValueError(f"Query error in {t_name}: {e}") diff --git a/examples/ndp/ndp_developer/1.ingest.py b/examples/ndp/ndp_developer/1.load_basic.py similarity index 86% rename from examples/ndp/ndp_developer/1.ingest.py rename to examples/ndp/ndp_developer/1.load_basic.py index 95cad4e4..5abcd9d1 100644 --- a/examples/ndp/ndp_developer/1.ingest.py +++ b/examples/ndp/ndp_developer/1.load_basic.py @@ -1,10 +1,11 @@ +# examples/ndp/ndp_developer/1.load_basic.py from dsi.core import Terminal def main(verbose=False): terminal = Terminal() # Load NDP backend (read-only) - terminal.load_module("backend", "NDP", "back-read", params={"keywords": "data", "limit": 5}) + terminal.load_module("backend", "NDP", "back-read", params={"keywords": "temperature", "limit": 5}) backend = terminal.active_modules["back-read"][0] diff --git a/examples/ndp/ndp_developer/2.list_summary.py b/examples/ndp/ndp_developer/2.list_summary.py new file mode 100644 index 00000000..fcd8410a --- /dev/null +++ b/examples/ndp/ndp_developer/2.list_summary.py @@ -0,0 +1,35 @@ +# examples/ndp/ndp_developer/2.list_summary.py +import argparse +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "energy", "limit": 10} + ) + + backend = terminal.active_modules["back-read"][0] + + if verbose: + print("\n=== List Tables ===") + backend.list(collection=False) # Prints table info + + print("\n=== Summary of Tables ===") + summary_df = backend.summary() + print(summary_df) + + # Get collection format + tables_dict = backend.list(collection=True) + print("\n=== Table Collections ===") + print(f"Dataset tables: {tables_dict['datasets']}") + print(f"Resource tables: {tables_dict['resources'][:3]}...") # Show first 3 + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="NDP list and summary example") + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/2.process.py b/examples/ndp/ndp_developer/2.process.py deleted file mode 100644 index c3ea8234..00000000 --- a/examples/ndp/ndp_developer/2.process.py +++ /dev/null @@ -1,39 +0,0 @@ -import argparse -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - # 1. Load backend + trigger initial CKAN fetch (INGEST STEP) - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "tags": ["temperature", "humidity"], - "organization": "NASA", - "formats": ["CSV", "JSON"], - "limit": 10 - } - ) - - backend = terminal.active_modules["back-read"][0] - - # 3. Process and print out data - tables = backend.process_artifacts() - - if verbose: - print("\nProcessed artifacts:\n") - - for table_name, table_data in tables.items(): - print(f"\n[{table_name}]") - - for col, values in table_data.items(): - print(f" {col}: {values[:3]}") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP process example") - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/3.display.py b/examples/ndp/ndp_developer/3.display.py new file mode 100644 index 00000000..ef0ebbac --- /dev/null +++ b/examples/ndp/ndp_developer/3.display.py @@ -0,0 +1,35 @@ +# examples/ndp/ndp_developer/3.display.py +import argparse +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "water", "limit": 8} + ) + + backend = terminal.active_modules["back-read"][0] + + if verbose: + print("\n=== Display datasets table ===") + backend.display("datasets", num_rows=5) + + # Display specific columns + print("\n=== Display datasets (specific columns) ===") + backend.display("datasets", num_rows=3, display_cols=["title", "organization", "num_resources"]) + + # Display a resource table if available + if backend._resource_tables: + resource_table = backend._resource_tables[0] + print(f"\n=== Display {resource_table} ===") + backend.display(resource_table, num_rows=5, display_cols=["resource_name", "format", "url"]) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="NDP display example") + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/4.find.py b/examples/ndp/ndp_developer/4.find.py deleted file mode 100644 index 7354fb93..00000000 --- a/examples/ndp/ndp_developer/4.find.py +++ /dev/null @@ -1,25 +0,0 @@ -# examples/ndp/ndp_developer/4.find.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "climate", "limit": 5}) - - tables_found = backend.find_table("datasets") - columns_found = backend.find_column("title") - cells_found = backend.find_cell("Canada") - - if verbose: - print("Tables matching 'datasets':", [v.t_name for v in tables_found]) - print("Columns matching 'title':", [v.c_name for v in columns_found]) - print("Cells matching 'Canada':", [(v.t_name, v.c_name, v.value) for v in cells_found]) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP find example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/3.query.py b/examples/ndp/ndp_developer/4.query.py similarity index 96% rename from examples/ndp/ndp_developer/3.query.py rename to examples/ndp/ndp_developer/4.query.py index 5f45c21f..5da28c82 100644 --- a/examples/ndp/ndp_developer/3.query.py +++ b/examples/ndp/ndp_developer/4.query.py @@ -1,3 +1,4 @@ +# examples/ndp/ndp_developer4.query.py import argparse from dsi.core import Terminal diff --git a/examples/ndp/ndp_developer/5.find.py b/examples/ndp/ndp_developer/5.find.py new file mode 100644 index 00000000..a702da9f --- /dev/null +++ b/examples/ndp/ndp_developer/5.find.py @@ -0,0 +1,41 @@ +# examples/ndp/ndp_developer/5.find.py +import argparse +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + # Load NDP backend with initial data + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 10} + ) + + backend = terminal.active_modules["back-read"][0] + + # Find methods work on the cached in-memory data + tables_found = backend.find_table("datasets") + columns_found = backend.find_column("title") + cells_found = backend.find_cell("Canada") + + if verbose: + print("\n=== Find Results ===") + print(f"\nTables matching 'datasets': {len(tables_found)}") + for v in tables_found: + print(f" - {v.t_name} (columns: {', '.join(v.c_name[:3])}...)") + + print(f"\nColumns matching 'title': {len(columns_found)}") + for v in columns_found[:5]: # Show first 5 + print(f" - Table: {v.t_name}, Column: {v.c_name[0]}") + + print(f"\nCells matching 'Canada': {len(cells_found)}") + for v in cells_found[:5]: # Show first 5 + print(f" - Table: {v.t_name}, Column: {v.c_name[0]}, Row: {v.row_num}, Value: {v.value}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="NDP find example") + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/5.inspect.py b/examples/ndp/ndp_developer/5.inspect.py deleted file mode 100644 index 717a3a08..00000000 --- a/examples/ndp/ndp_developer/5.inspect.py +++ /dev/null @@ -1,26 +0,0 @@ -# examples/ndp/ndp_developer/5.inspect.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "climate", "limit": 5}) - - meta = backend.inspect_artifacts() - artifacts_1 = backend.get_artifacts() - artifacts_2 = backend.read_to_artifacts() - - if verbose: - print("Inspect metadata summary:", meta) - print("Artifacts (get_artifacts):", {k: list(v.keys()) for k, v in artifacts_1.items()}) - print("Artifacts (read_to_artifacts):", {k: list(v.keys()) for k, v in artifacts_2.items()}) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP inspect example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) - \ No newline at end of file diff --git a/examples/ndp/ndp_developer/6.process.py b/examples/ndp/ndp_developer/6.process.py new file mode 100644 index 00000000..cbd81628 --- /dev/null +++ b/examples/ndp/ndp_developer/6.process.py @@ -0,0 +1,32 @@ +# examples/ndp/ndp_developer/6.process.py +import argparse +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + # Load NDP backend with initial query + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 10} + ) + + backend = terminal.active_modules["back-read"][0] + + # Process artifacts returns the cached tables (datasets + resource tables) + tables = backend.process_artifacts() + + if verbose: + print("\nProcessed artifacts (cached tables):\n") + for table_name in tables.keys(): + num_rows = len(tables[table_name].get(list(tables[table_name].keys())[0], [])) + num_cols = len(tables[table_name].keys()) + print(f" [{table_name}]: {num_rows} rows, {num_cols} columns") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="NDP process example") + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/6.validate.py b/examples/ndp/ndp_developer/6.validate.py deleted file mode 100644 index 513e80f4..00000000 --- a/examples/ndp/ndp_developer/6.validate.py +++ /dev/null @@ -1,35 +0,0 @@ -# examples/ndp/ndp_developer/6.validate.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "wildfire", "limit": 8}) - artifacts = backend.process_artifacts() - - if verbose: - print("Datasets loaded:", list(artifacts["datasets"].keys())) - print("Resources loaded:", list(artifacts["resources"].keys())) - - backend.validate_urls() - - resources = artifacts.get("resources", {}) - urls = resources.get("url", []) - url_valid = resources.get("url_valid", []) - - if urls: - for i, (u, v) in enumerate(zip(urls, url_valid)): - if verbose or i < 5: - print(f"{i+1}. {u} -> {'Valid' if v else 'Invalid'}") - else: - if verbose: - print("No resources found. Try increasing 'limit' or check your query.") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP URL validation example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/7.notebook.py b/examples/ndp/ndp_developer/7.notebook.py deleted file mode 100644 index 170def3e..00000000 --- a/examples/ndp/ndp_developer/7.notebook.py +++ /dev/null @@ -1,19 +0,0 @@ -# examples/ndp/ndp_developer/7.notebook.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "wildfire", "limit": 5}) - - if verbose: - backend.notebook() - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP notebook preview example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/7.validate.py b/examples/ndp/ndp_developer/7.validate.py new file mode 100644 index 00000000..d702c57a --- /dev/null +++ b/examples/ndp/ndp_developer/7.validate.py @@ -0,0 +1,51 @@ +# examples/ndp/ndp_developer/7.validate.py +import argparse +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + backend = terminal.active_modules["back-read"][0] + + # Process artifacts to get the tables + artifacts = backend.process_artifacts() + + if verbose: + print("\n=== Before Validation ===") + print(f"Datasets loaded: {len(artifacts.get('datasets', {}).get('id', []))}") + + # Count total resources across all resource tables + total_resources = sum( + len(table.get('url', [])) + for name, table in artifacts.items() + if name.startswith('resources_') + ) + print(f"Total resources: {total_resources}") + + # Validate URLs (adds 'url_valid' column to resource tables) + backend.validate_urls() + + if verbose: + print("\n=== After Validation ===") + for table_name in backend._resource_tables[:3]: # Show first 3 resource tables + table = artifacts.get(table_name, {}) + urls = table.get("url", []) + url_valid = table.get("url_valid", []) + + print(f"\n{table_name}:") + for i, (url, valid) in enumerate(zip(urls[:3], url_valid[:3])): # First 3 URLs + status = "✓ Valid" if valid else "✗ Invalid" + print(f" {i+1}. {status}: {url[:60]}...") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="NDP URL validation example") + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/8.close.py b/examples/ndp/ndp_developer/8.close.py index d73ed7fe..c68fb385 100644 --- a/examples/ndp/ndp_developer/8.close.py +++ b/examples/ndp/ndp_developer/8.close.py @@ -3,19 +3,35 @@ from dsi.core import Terminal def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + backend = terminal.active_modules["back-read"][0] - backend.query_artifacts(query=None, kwargs={"keywords": "climate", "limit": 5}) + if verbose: + print("\n=== Before close ===") + print(f"Loaded: {backend._loaded}") + print(f"Cached tables: {list(backend._cache.keys())}") + summary = backend.summary() + print(f"Number of tables: {len(summary)}") + # Close resets the backend state backend.close() if verbose: - print("Backend state after close:", backend.inspect_artifacts()) + print("\n=== After close ===") + print(f"Loaded: {backend._loaded}") + print(f"Cached tables: {list(backend._cache.keys())}") + print(f"Resource tables: {backend._resource_tables}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="NDP close/reset example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") + parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/9.run_all.py b/examples/ndp/ndp_developer/9.run_all.py index a27852a8..43ecdc67 100644 --- a/examples/ndp/ndp_developer/9.run_all.py +++ b/examples/ndp/ndp_developer/9.run_all.py @@ -2,13 +2,13 @@ import subprocess test_files = [ - "1.ingest.py", - "2.process.py", - "3.query.py", - "4.find.py", - "5.inspect.py", - "6.validate.py", - "7.notebook.py", + "1.load_basic.py", + "2.list_summary.py", + "3.display.py", + "4.query.py", + "5.find.py", + "6.process.py", + "7.validate.py", "8.close.py" ] From e02d6412cabfd16b56234849ad9897bf4917cff6 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 09:58:44 -0600 Subject: [PATCH 053/109] refactor: updaated tests --- .github/workflows/test_ndp.yml | 6 +- dsi/backends/tests/test_ndp.py | 443 +++++++++++++++++++++++++++------ 2 files changed, 367 insertions(+), 82 deletions(-) diff --git a/.github/workflows/test_ndp.yml b/.github/workflows/test_ndp.yml index 509ce115..e107e919 100644 --- a/.github/workflows/test_ndp.yml +++ b/.github/workflows/test_ndp.yml @@ -30,7 +30,9 @@ jobs: pip install -r requirements.extras.txt pip install . - - name: Test NDP + - name: Test NDP Backend run: | pip install pytest - pytest dsi/backends/tests/test_ndp.py \ No newline at end of file + pytest dsi/backends/tests/test_ndp.py -v --tb=short + env: + PYTHONUNBUFFERED: 1 \ No newline at end of file diff --git a/dsi/backends/tests/test_ndp.py b/dsi/backends/tests/test_ndp.py index 006fea8b..524f9694 100644 --- a/dsi/backends/tests/test_ndp.py +++ b/dsi/backends/tests/test_ndp.py @@ -1,129 +1,412 @@ -# tests/test_ndp.py +# dsi/backends/tests/test_ndp.py from collections import OrderedDict -from dsi.backends.ndp import NDP +from dsi.core import Terminal +import pytest # ---------------------------- -# 1) Test querying artifacts +# 1) Test loading NDP backend with specific parameters # ---------------------------- -def test_ndp_query_artifacts(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "climate", "limit": 5}) - artifacts = backend.process_artifacts() - assert "datasets" in artifacts - assert "resources" in artifacts - backend.close() +def test_ndp_load_and_query(): + """Test NDP backend loads with specific climate data parameters.""" + terminal = Terminal() + + # Load NDP backend with climate search + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate temperature precipitation", + "organization": "noaa-gov", + "limit": 10 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Verify data was loaded + assert backend._loaded is True + assert len(backend._cache) > 0 + assert "datasets" in backend._cache + + terminal.close() + # ---------------------------- # 2) Test process_artifacts structure # ---------------------------- def test_ndp_process_structure(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "energy", "limit": 5}) + """Test that processed artifacts have correct tiered structure.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "ocean temperature", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] artifacts = backend.process_artifacts() - assert isinstance(artifacts["datasets"], dict) - assert isinstance(artifacts["resources"], dict) - backend.close() + + # Verify tiered structure + assert isinstance(artifacts, OrderedDict) + assert "datasets" in artifacts + assert isinstance(artifacts["datasets"], OrderedDict) + + # Check dataset table has expected columns + dataset_cols = list(artifacts["datasets"].keys()) + assert "id" in dataset_cols + assert "title" in dataset_cols + assert "num_resources" in dataset_cols + + terminal.close() + # ---------------------------- -# 3) Test query_in_memory +# 3) Test query_artifacts with pandas query # ---------------------------- -def test_ndp_query_in_memory(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "science", "limit": 5}) - result = backend.query_in_memory("`num_resources` >= 0", {"table": "datasets"}) - assert isinstance(result, OrderedDict) - assert all(len(v) > 0 for v in result.values()) - backend.close() +def test_ndp_query_with_pandas(): + """Test querying loaded data with pandas query string.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate data", + "limit": 10 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Query datasets with resources + result = backend.query_artifacts("num_resources > 0", dict_return=True) + + assert isinstance(result, dict) + # Should have results if any datasets have resources + if result: + assert "datasets" in result or any("resources_" in key for key in result.keys()) + + terminal.close() + # ---------------------------- -# 4) Test invalid query_in_memory raises +# 4) Test invalid pandas query raises error # ---------------------------- def test_ndp_query_invalid(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "science", "limit": 5}) - try: - backend.query_in_memory("INVALID QUERY ###") - except ValueError: - pass - else: - assert False, "Invalid query did not raise ValueError" - backend.close() + """Test that invalid pandas queries raise appropriate errors.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "temperature", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + with pytest.raises(ValueError): + backend.query_artifacts("INVALID SYNTAX ###") + + terminal.close() + # ---------------------------- -# 5) Test find_table / find_column / find_cell +# 5) Test find methods # ---------------------------- -def test_ndp_find_table_column_cell(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "science", "limit": 5}) - +def test_ndp_find_operations(): + """Test find operations across tables, columns, and cells.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate temperature", + "limit": 8 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Test finding tables tables_found = backend.find_table("datasets") + assert len(tables_found) > 0 assert any("datasets" in t.t_name for t in tables_found) - + + # Test finding columns columns_found = backend.find_column("title") + assert len(columns_found) > 0 assert any("title" in c.c_name for c in columns_found) - - cells_found = backend.find_cell("Canada") + + # Test finding cells + cells_found = backend.find_cell("climate") if cells_found: - for cell in cells_found: - assert cell.type == "cell" + assert all(hasattr(cell, 'type') and cell.type == "cell" for cell in cells_found) + + terminal.close() - backend.close() # ---------------------------- # 6) Test combined find() # ---------------------------- def test_ndp_find_combined(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "science", "limit": 5}) + """Test the general find() method that searches everywhere.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "ocean data", + "limit": 7 + } + ) + + backend = terminal.active_modules["back-read"][0] + results = backend.find("title") assert isinstance(results, list) - backend.close() + + terminal.close() + # ---------------------------- -# 7) Test URL validation +# 7) Test URL validation on resource tables # ---------------------------- def test_ndp_validate_urls(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "transport", "limit": 5}) + """Test URL validation for dataset resources.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate precipitation", + "organization": "noaa-gov", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] backend.validate_urls() - resources = backend.process_artifacts()["resources"] - assert "url_valid" in resources - assert all(isinstance(v, bool) for v in resources["url_valid"]) - assert len(resources["url_valid"]) == len(resources["url"]) - backend.close() + + # Check that url_valid was added to resource tables + for table_name in backend._resource_tables: + if table_name in backend._cache: + table = backend._cache[table_name] + if "url" in table and len(table["url"]) > 0: + assert "url_valid" in table + assert all(isinstance(v, bool) for v in table["url_valid"]) + assert len(table["url_valid"]) == len(table["url"]) + + terminal.close() + # ---------------------------- -# 8) Test notebook / inspect_artifacts +# 8) Test list and summary methods # ---------------------------- -def test_ndp_notebook_and_inspect(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "environment", "limit": 5}) - backend.notebook() # ensure no exceptions - meta = backend.inspect_artifacts() - assert meta["loaded"] is True - assert "datasets" in meta["tables"] - assert "resources" in meta["tables"] - backend.close() +def test_ndp_list_and_summary(): + """Test metadata retrieval methods.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "atmospheric pressure", + "limit": 6 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Test list with collection=True + table_info = backend.list(collection=True) + assert isinstance(table_info, dict) + assert "datasets" in table_info + assert "resources" in table_info + assert isinstance(table_info["resources"], list) + + # Test summary + summary_df = backend.summary() + assert not summary_df.empty + assert "table_name" in summary_df.columns + assert "num_rows" in summary_df.columns + + terminal.close() + # ---------------------------- # 9) Test read-only enforcement # ---------------------------- def test_ndp_read_only(): - backend = NDP() - try: - backend.put_artifacts({}, {}) - except NotImplementedError: - pass - else: - assert False, "put_artifacts did not raise NotImplementedError" - backend.close() + """Test that NDP backend is properly read-only.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "data", + "limit": 3 + } + ) + + backend = terminal.active_modules["back-read"][0] + + with pytest.raises(NotImplementedError): + backend.ingest_artifacts({}) + + terminal.close() + + +# ---------------------------- +# 10) Test display method +# ---------------------------- +def test_ndp_display(): + """Test display method for tables.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate ocean", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Display datasets table + result = backend.display("datasets", num_rows=10) + assert result is not None + assert len(result) <= 10 + + terminal.close() + + +# ---------------------------- +# 11) Test with different organizations +# ---------------------------- +def test_ndp_organization_filtering(): + """Test querying with organization filter.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "organization": "noaa-gov", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + data = backend.process_artifacts() + + assert "datasets" in data + assert len(data["datasets"]) > 0 + + terminal.close() + + +# ---------------------------- +# 12) Test with tags filter +# ---------------------------- +def test_ndp_tags_filtering(): + """Test querying with tags filter.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "temperature", + "tags": ["climate", "weather"], + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + assert backend._loaded is True + data = backend.process_artifacts() + assert "datasets" in data + + terminal.close() + # ---------------------------- -# 10) Test metadata helpers +# 13) Test with format filter # ---------------------------- -def test_ndp_metadata_methods(): - backend = NDP() - backend.query_artifacts(None, {"keywords": "earth", "limit": 3}) - assert isinstance(backend.get_artifacts(), dict) - assert isinstance(backend.read_to_artifacts(), dict) - backend.close() \ No newline at end of file +def test_ndp_format_filtering(): + """Test querying with resource format filter.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "data", + "formats": ["CSV", "JSON"], + "limit": 10 + } + ) + + backend = terminal.active_modules["back-read"][0] + + assert backend._loaded is True + + terminal.close() + + +# ---------------------------- +# 14) Test close method +# ---------------------------- +def test_ndp_close(): + """Test that close() properly resets backend state.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + assert backend._loaded is True + assert len(backend._cache) > 0 + + backend.close() + + assert backend._loaded is False + assert len(backend._cache) == 0 + assert len(backend._resource_tables) == 0 + + terminal.close() \ No newline at end of file From 649211d6dfc9c20071e11f2a711bf72c6fe90021 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 10:46:49 -0600 Subject: [PATCH 054/109] refactor: added todos --- dsi/backends/ndp.py | 3 +-- dsi/cli.py | 1 + dsi/core.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 6fd39e83..2e3bd3ca 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -148,7 +148,7 @@ def _load_initial_data(self, params): # Tier 2: per-dataset resource tables self._resource_tables = [] for dataset_name, rows in resource_map.items(): - table_name = f"resources_{dataset_name}" + table_name = dataset_name self._cache[table_name] = self._rows_to_table(rows) self._resource_tables.append(table_name) @@ -314,7 +314,6 @@ def validate_urls(self): Validates resource URLs across all resource tables. Adds 'url_valid' column. """ - headers = {"User-Agent": "NDP-Validator"} for table_name in self._resource_tables: diff --git a/dsi/cli.py b/dsi/cli.py index 307d6757..b22e8131 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -107,6 +107,7 @@ def startup(self, backend="sqlite"): if backend=="duckdb": self.t.load_module('backend','DuckDB','back-write', filename = self.db_path) self.name = "duckdb" + # TODO: Add NDP else: backend = "sqlite" self.t.load_module('backend','Sqlite','back-write', filename = self.db_path) diff --git a/dsi/core.py b/dsi/core.py index fb2c9353..ab9fd6a7 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1397,6 +1397,7 @@ def valid_backend(self, backend, parent_name): return True elif parent_name == "Webserver": if backend.__class__.__name__ == "NDP": + # TODO: Use URL validation to see if NDP goes down, then let it pass return True return False From 7873f50093e57a8882b5910990accba68ee7de04 Mon Sep 17 00:00:00 2001 From: Vedant1 Date: Thu, 16 Apr 2026 10:56:21 -0600 Subject: [PATCH 055/109] when list(collection=True) for ndp, return table name list --- dsi/backends/ndp.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 2e3bd3ca..5d49c781 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -549,10 +549,7 @@ def list(self, collection=False): """ if collection: - return { - "datasets": ["datasets"], - "resources": self._resource_tables - } + return self._cache.keys() for name, table in self._cache.items(): df = pd.DataFrame(table) From 47ef97c0c1757dd97d35d805e5b95f6948e95f39 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 14:34:26 -0600 Subject: [PATCH 056/109] refactor: update todo --- dsi/backends/ndp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 5d49c781..b1dc8798 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -273,7 +273,7 @@ def query_artifacts(self, query, dict_return=True, **kwargs): result_df.to_dict(orient="list") if dict_return else result_df ) - + # TODO: If none, throw error except pd.errors.UndefinedVariableError: # Skip tables that don't have the queried columns continue From 793482155a4b105d33246be6ecb94f673e5179c9 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 23:11:04 -0600 Subject: [PATCH 057/109] features: added dataset id to queries and displays + id map --- dsi/backends/ndp.py | 718 +++++++++++++++++++++++++++++++++++--------- dsi/cli.py | 6 +- dsi/core.py | 15 +- 3 files changed, 599 insertions(+), 140 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index b1dc8798..6e929484 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -16,19 +16,25 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) -# --------------------------------------------------------- +# ---------------------------------------------------------------------- # Value Object (used for search results) -# --------------------------------------------------------- +# ---------------------------------------------------------------------- class ValueObject: """ Container for search results returned by find* methods - Attributes: - t_name : table name - c_name : column name(s) - row_num : row index (if applicable) - value : matched value - type : {table, column, cell} + Attributes + ---------- + t_name : str + Table name + c_name : list + Column name(s) + row_num : int or None + Row index (if applicable) + value : any + Matched value + type : str + {'table', 'column', 'cell'} """ def __init__(self): self.t_name = "" @@ -38,25 +44,26 @@ def __init__(self): self.type = "" -# --------------------------------------------------------- +# ---------------------------------------------------------------------- # NDP Backend (Webserver - Read only) -# --------------------------------------------------------- +# ---------------------------------------------------------------------- class NDP(Webserver): """ CKAN-based web backend for querying NDP metadata in-memory """ - # ---------------------------- + # ---------------------------------------------------------------------- # Initialization - # ---------------------------- + # ---------------------------------------------------------------------- def __init__(self, url=None, params=None, **kwargs): """ Initialize backend and optionally load data from CKAN API. - url : + Parameters + ---------- + `url` : str, optional Base CKAN URL. If None, a default CKAN endpoint is used. - - params : + `params` : dict, optional Dictionary of initial query parameters used to fetch data from CKAN. Supported keys: - keywords @@ -64,19 +71,21 @@ def __init__(self, url=None, params=None, **kwargs): - tags - formats - limit - - kwargs : - api_key : optional API key - verify_ssl : toggle SSL verification (default False) + `**kwargs` : dict + Additional keyword arguments: + - api_key : str, optional + API key for authentication + - verify_ssl : bool, optional + Toggle SSL verification (default False) """ DEFAULT_URL = "https://nationaldataplatform.org/catalog" base_url = url or DEFAULT_URL - # ---------------------------- + # ---------------------------------------------------------------------- # Auth / connection config - # ---------------------------- + # ---------------------------------------------------------------------- self.api_key = kwargs.get("api_key") self.verify_ssl = kwargs.get("verify_ssl", False) @@ -91,10 +100,11 @@ def __init__(self, url=None, params=None, **kwargs): self.headers["Authorization"] = self.api_key # Data storage (tiered structure) - # Tier 1: datasets - # Tier 2: per-dataset resource tables + # Tier 1: datasets, Tier 2: per-dataset resource tables self._cache = OrderedDict() self._resource_tables = [] + self._dataset_id_map = {} + self._dataset_title_map = {} self._loaded = False self.params = params or {} @@ -102,17 +112,96 @@ def __init__(self, url=None, params=None, **kwargs): # Initial data load if self.params: self._load_initial_data(self.params) - - - # --------------------------------------------------- + + + # ---------------------------------------------------------------------- + # Connection Validation + # ---------------------------------------------------------------------- + def validate_connection(self): + """ + Validates that the base CKAN URL is accessible and functional. + + This method tests the connection by making a simple API call to verify: + - The URL is reachable + - The CKAN API is responding + + Raises + ------ + ConnectionError + If the URL cannot be reached + RuntimeError + If the CKAN API returns an error response + + Returns + ------- + bool + True if connection is valid + """ + try: + test_url = f"{self.base_url}/api/3/action/status_show" + + response = requests.get( + test_url, + headers=self.headers, + verify=self.verify_ssl, + timeout=10 + ) + + response.raise_for_status() + data = response.json() + + if not data.get("success"): + raise RuntimeError( + f"CKAN API at {self.base_url} returned failure response" + ) + + return True + + except requests.exceptions.Timeout: + raise ConnectionError( + f"Connection timeout: Unable to reach {self.base_url} within 10 seconds" + ) + except requests.exceptions.ConnectionError: + raise ConnectionError( + f"Connection failed: Unable to connect to {self.base_url}. " + "Check the URL and your network connection." + ) + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + raise RuntimeError( + f"CKAN API not found at {self.base_url}. " + "Verify this is a valid CKAN endpoint." + ) + else: + raise RuntimeError( + f"HTTP {e.response.status_code} Error: {str(e)}" + ) + except requests.exceptions.RequestException as e: + raise ConnectionError( + f"Failed to validate connection to {self.base_url}: {str(e)}" + ) + except ValueError as e: + raise RuntimeError( + f"Invalid JSON response from {self.base_url}: {str(e)}" + ) + + + # ---------------------------------------------------------------------- # Initial Data Load - # --------------------------------------------------- + # ---------------------------------------------------------------------- def _load_initial_data(self, params): """ Fetch datasets/resources from CKAN API and store in memory. - `params`: - keywords, organization, tags, formats, limit + Parameters + ---------- + `params` : dict + Query parameters including: + - keywords : str, optional + - organization : str, optional + - tags : list, optional + - formats : list, optional + - limit : int, optional """ query_params = {"rows": params.get("limit", 100)} @@ -140,27 +229,89 @@ def _load_initial_data(self, params): result = self._request("package_search", query_params) - dataset_rows, resource_map = self._extract_tables(result.get("results", [])) + dataset_rows, resource_map, id_map = self._extract_tables(result.get("results", [])) # Tier 1: datasets self._cache["datasets"] = self._rows_to_table(dataset_rows) + + self._dataset_id_map = id_map + self._dataset_title_map = {v: k for k, v in id_map.items()} # Tier 2: per-dataset resource tables self._resource_tables = [] - for dataset_name, rows in resource_map.items(): - table_name = dataset_name + for dataset_title, rows in resource_map.items(): + table_name = dataset_title self._cache[table_name] = self._rows_to_table(rows) self._resource_tables.append(table_name) self._loaded = True - # --------------------------------------------------- + # ---------------------------------------------------------------------- + # Table Name Resolution + # ---------------------------------------------------------------------- + def _resolve_table_name(self, identifier): + """ + Resolves a table identifier to its canonical name. + + Accepts either dataset_title or dataset_id for resource tables. + + Parameters + ---------- + `identifier` : str + Table name (dataset_title) or dataset ID + + Returns + ------- + str + Canonical table name (dataset_title) + + Raises + ------ + ValueError + If the identifier cannot be resolved to a table + """ + if identifier in self._cache: + return identifier + + if identifier in self._dataset_id_map: + return self._dataset_id_map[identifier] + + available_titles = [name for name in self._resource_tables] + + error_msg = f"Table '{identifier}' not found.\n" + + if available_titles: + error_msg += f"\nAvailable dataset titles:\n" + for title in available_titles[:5]: + dataset_id = self._dataset_title_map.get(title, "N/A") + error_msg += f" - '{title}' (ID: {dataset_id})\n" + if len(available_titles) > 5: + error_msg += f" ... and {len(available_titles) - 5} more\n" + + if "datasets" in self._cache: + error_msg += "\nOr use 'datasets' to view all datasets." + + raise ValueError(error_msg) + + # ---------------------------------------------------------------------- # API Helpers - # --------------------------------------------------- + # ---------------------------------------------------------------------- def _request(self, endpoint, params=None): """ Execute GET request against CKAN API. + + Parameters + ---------- + `endpoint` : str + CKAN API endpoint name + `params` : dict, optional + Query parameters for the request + + Returns + ------- + dict + Result data from CKAN API response """ url = f"{self.base_url}/api/3/action/{endpoint}" @@ -183,21 +334,41 @@ def _request(self, endpoint, params=None): def _extract_tables(self, datasets): """ - Flatten CKAN dataset JSON into: - - dataset_rows (list of dicts) - - resource_map (dict: dataset_name -> list of resource rows) + Flatten CKAN dataset JSON into dataset and resource tables. + + Parameters + ---------- + `datasets` : list + List of dataset dictionaries from CKAN API + + Returns + ------- + tuple + (dataset_rows, resource_map, id_map) where: + - dataset_rows : list of dict + Flattened dataset metadata + - resource_map : dict + Maps dataset_name to list of resource rows + - id_map : dict + Maps dataset_id to dataset_title """ dataset_rows = [] resource_map = {} + id_map = {} for ds in datasets: - dataset_name = ds.get("name") or ds.get("id") + dataset_id = ds.get("id") + dataset_name = ds.get("name") or dataset_id + dataset_title = ds.get("title") or dataset_name + + if dataset_id and dataset_title: + id_map[dataset_id] = dataset_title dataset_rows.append({ - "id": ds.get("id"), + "id": dataset_id, "name": dataset_name, - "title": ds.get("title"), + "title": dataset_title, "notes": ds.get("notes"), "organization": (ds.get("organization") or {}).get("title"), "author": ds.get("author"), @@ -208,25 +379,35 @@ def _extract_tables(self, datasets): "num_resources": ds.get("num_resources", 0) }) - resource_map.setdefault(dataset_name, []) - + resource_map.setdefault(dataset_title, []) + for r in ds.get("resources", []): - resource_map[dataset_name].append({ + resource_map[dataset_title].append({ "resource_id": r.get("id"), "resource_name": r.get("name"), "format": r.get("format"), "size": r.get("size"), "url": r.get("url"), - "dataset_id": ds.get("id"), - "dataset_title": ds.get("title") + "dataset_id": dataset_id, + "dataset_title": dataset_title }) - return dataset_rows, resource_map + return dataset_rows, resource_map, id_map def _rows_to_table(self, rows): """ - Convert list-of-dicts → column-oriented OrderedDict. + Convert list-of-dicts to column-oriented OrderedDict. + + Parameters + ---------- + `rows` : list of dict + Row data as list of dictionaries + + Returns + ------- + OrderedDict + Column-oriented table structure """ if not rows: @@ -242,20 +423,140 @@ def _rows_to_table(self, rows): return table - # --------------------------------------------------- + # ---------------------------------------------------------------------- + # Terminal Methods + # ---------------------------------------------------------------------- + def num_tables(self): + """ + Prints the number of tables (datasets) loaded. + """ + if not self._loaded: + print("0 tables loaded") + return + + num = len(self._cache) - (1 if "datasets" in self._cache else 0) + print(f"{num} tables loaded") + + + def get_table(self, table_name, dict_return=False): + """ + Returns all data from a specified table. + + Parameters + ---------- + `table_name` : str + Dataset title or ID + `dict_return` : bool, default False + If True, returns OrderedDict. If False, returns DataFrame. + + Returns + ------- + OrderedDict or pandas.DataFrame + """ + if not self._loaded: + raise RuntimeError("No data loaded. Call load_datasets() first.") + + resolved_name = self._resolve_table_name(table_name) + table = self._cache.get(resolved_name) + + if not table: + raise ValueError(f"Table '{resolved_name}' is empty") + + if dict_return: + return table + return pd.DataFrame(table) + + + def get_schema(self): + """NDP does not store structural schema - data comes from CKAN API.""" + return ( + "-- NDP Backend Schema Information\n" + "-- NDP is a read-only CKAN metadata backend\n" + "-- Data is retrieved dynamically from the API\n" + "-- Use summary() or list() to view available tables and columns\n" + ) + + + def get_table_names(self, query): + """ + Extracts table/dataset names mentioned in a query string. + + Parameters + ---------- + `query` : str + Query string to parse + + Returns + ------- + list + List of dataset names/IDs found in query + """ + if not self._loaded: + return [] + + import re + + pattern = r'\b[a-zA-Z_][a-zA-Z0-9_-]*\b' + words = re.findall(pattern, query) + + found_tables = [] + for word in words: + if word in self._cache: + found_tables.append(word) + elif word in self._dataset_id_map: + found_tables.append(self._dataset_id_map[word]) + + return list(set(found_tables)) + + + def overwrite_table(self, table_name, collection): + """ + Not supported - NDP backend is read-only. + + Parameters + ---------- + `table_name` : str or list + Table name(s) + `collection` : DataFrame or list + Data + + Raises + ------ + NotImplementedError + Always raised as NDP is read-only + """ + raise NotImplementedError( + "NDP backend is read-only. Cannot overwrite tables. " + "To modify data, use artifact_handler('process') to load into " + "a writable backend (Sqlite/DuckDB), make changes, then query." + ) + + + # ---------------------------------------------------------------------- # Query Interface (in-memory) - # --------------------------------------------------- + # ---------------------------------------------------------------------- def query_artifacts(self, query, dict_return=True, **kwargs): """ Query all tables using a pandas query string. - `dict_return` : - If True, returns dict format (default). + Parameters + ---------- + `query` : str + Pandas query string for filtering data + `dict_return` : bool, default True + If True, returns dict format. If False, returns pandas DataFrames. + `**kwargs` : dict + Additional keyword arguments + + Returns + ------- + dict + Dictionary mapping table names to query results """ if not self._loaded: - return {} + raise RuntimeError("No data loaded. Cannot query empty backend.") results = {} @@ -273,19 +574,20 @@ def query_artifacts(self, query, dict_return=True, **kwargs): result_df.to_dict(orient="list") if dict_return else result_df ) - # TODO: If none, throw error except pd.errors.UndefinedVariableError: - # Skip tables that don't have the queried columns continue except Exception as e: raise ValueError(f"Query error in {t_name}: {e}") + if not results: + raise ValueError(f"Query returned no results: '{query}'") + return results - # --------------------------------------------------- + # ---------------------------------------------------------------------- # Artifact Processing (tiered table construction) - # --------------------------------------------------- + # ---------------------------------------------------------------------- def process_artifacts(self): """ Returns all cached tables in tiered format. @@ -298,6 +600,11 @@ def process_artifacts(self): } Useful for exporting or writing to external systems. + + Returns + ------- + OrderedDict + All cached tables in tiered structure """ if not self._loaded: @@ -306,13 +613,13 @@ def process_artifacts(self): return self._cache - # --------------------------------------------------- + # ---------------------------------------------------------------------- # URL Validation - # --------------------------------------------------- + # ---------------------------------------------------------------------- def validate_urls(self): """ Validates resource URLs across all resource tables. - Adds 'url_valid' column. + Adds 'url_valid' column to each resource table. """ headers = {"User-Agent": "NDP-Validator"} @@ -349,28 +656,43 @@ def validate_urls(self): table["url_valid"] = valid_list - # --------------------------------------------------- + # ---------------------------------------------------------------------- # Find Methods - # --------------------------------------------------- + # ---------------------------------------------------------------------- def find(self, query_object, **kwargs): """ - Searches for all instances of `query_object` across all tables at the table, column, and cell levels. + Searches for all instances of query_object across all tables. + + Searches at the table, column, and cell levels. + Parameters + ---------- `query_object` : int, float, or str - The value to search for across all tables in the backend. + The value to search for across all tables in the backend + `**kwargs` : dict + Additional keyword arguments - `return` : list of ValueObjects + Returns + ------- + list of ValueObject A list of ValueObjects representing matches across: - table names - column names - cell values + Notes + ----- ValueObject Structure: - - t_name: table name (str) - - c_name: column name(s) (list) - - row_num: row index (int or None) - - value: matched value or data - - type: {'table', 'column', 'cell'} + - t_name : str + Table name + - c_name : list + Column name(s) + - row_num : int or None + Row index + - value : any + Matched value or data + - type : str + {'table', 'column', 'cell'} """ query_str = str(query_object).lower() @@ -384,20 +706,33 @@ def find(self, query_object, **kwargs): def find_table(self, query_object, **kwargs): """ - Finds all tables whose names contain the given `query_object` (case-insensitive). + Finds all tables whose names contain the given query_object. + + Search is case-insensitive. + Parameters + ---------- `query_object` : str - The string to match against table names. + The string to match against table names + `**kwargs` : dict + Additional keyword arguments - `return` : list of ValueObjects - One ValueObject per matching table. + Returns + ------- + list of ValueObject + One ValueObject per matching table + Notes + ----- ValueObject Structure: - - t_name: table name (str) - - c_name: list of all columns in the table - - value: full table data (dict of columns) - - row_num: None - - type: 'table' + - t_name : str + Table name + - c_name : list + List of all columns in the table + - value : dict + Full table data (dict of columns) + - row_num : None + - type : 'table' """ if not isinstance(query_object, str): @@ -413,7 +748,6 @@ def find_table(self, query_object, **kwargs): val.value = table_data val.type = "table" - matches.append(val) return matches @@ -421,20 +755,33 @@ def find_table(self, query_object, **kwargs): def find_column(self, query_object, **kwargs): """ - Finds all columns whose names contain the given `query_object` (case-insensitive). + Finds all columns whose names contain the given query_object. + + Search is case-insensitive. + Parameters + ---------- `query_object` : str - The string to match against column names. + The string to match against column names + `**kwargs` : dict + Additional keyword arguments - `return` : list of ValueObjects - One ValueObject per matching column. + Returns + ------- + list of ValueObject + One ValueObject per matching column + Notes + ----- ValueObject Structure: - - t_name: table name (str) - - c_name: list containing the matched column name - - value: full column data (list) - - row_num: None - - type: 'column' + - t_name : str + Table name + - c_name : list + List containing the matched column name + - value : list + Full column data + - row_num : None + - type : 'column' """ if not isinstance(query_object, str): @@ -452,7 +799,6 @@ def find_column(self, query_object, **kwargs): val.value = col_data val.type = "column" - matches.append(val) return matches @@ -460,24 +806,36 @@ def find_column(self, query_object, **kwargs): def find_cell(self, query_object, **kwargs): """ - Finds all cells that match the given `query_object`. + Finds all cells that match the given query_object. Matching behavior: - Exact match for all data types - Case-insensitive partial match for strings + Parameters + ---------- `query_object` : int, float, or str - The value to search for within table cells. + The value to search for within table cells + `**kwargs` : dict + Additional keyword arguments - `return` : list of ValueObjects - One ValueObject per matching cell. + Returns + ------- + list of ValueObject + One ValueObject per matching cell + Notes + ----- ValueObject Structure: - - t_name: table name (str) - - c_name: list containing the matched column name - - row_num: row index of the match (int) - - value: matched cell value - - type: 'cell' + - t_name : str + Table name + - c_name : list + List containing the matched column name + - row_num : int + Row index of the match + - value : any + Matched cell value + - type : 'cell' """ matches = [] @@ -491,18 +849,16 @@ def find_cell(self, query_object, **kwargs): continue cols = list(table_data.keys()) - rows = zip(*table_data.values()) # avoids creating full list + rows = zip(*table_data.values()) for row_idx, row in enumerate(rows): for col_idx, cell in enumerate(row): match = False - # Exact match if query_object == cell: match = True - # string partial match elif ( is_str_query and isinstance(cell, str) and @@ -518,11 +874,11 @@ def find_cell(self, query_object, **kwargs): val.value = cell val.type = "cell" - matches.append(val) return matches + def find_relation(self, column_name, relation, **kwargs): """ Not supported for NDP backend. @@ -530,22 +886,42 @@ def find_relation(self, column_name, relation, **kwargs): NDP is a read-only metadata backend and does not support relational queries on columns. - `return` : list - Always returns an empty list. + Parameters + ---------- + `column_name` : str + Column name (unused) + `relation` : str + Relation type (unused) + `**kwargs` : dict + Additional keyword arguments (unused) + + Returns + ------- + list + Always returns an empty list """ return [] - - - # --------------------------------------------------- + + + # ---------------------------------------------------------------------- # Utility / Display - # --------------------------------------------------- + # ---------------------------------------------------------------------- def list(self, collection=False): """ Lists tables or prints metadata. - collection : bool, default False - True → return list of table names - False → print table names + dimensions + For resource tables, displays both dataset_title and dataset_id. + + Parameters + ---------- + `collection` : bool, default False + If True, return list of table names. + If False, print table names with dimensions and dataset IDs. + + Returns + ------- + dict_keys or None + Table names if collection=True, otherwise None """ if collection: @@ -553,16 +929,36 @@ def list(self, collection=False): for name, table in self._cache.items(): df = pd.DataFrame(table) - print(f"{name}: ({len(df)} rows, {len(df.columns)} cols)") - + + if name in self._resource_tables: + dataset_id = self._dataset_title_map.get(name, "N/A") + print(f"{name} (ID: {dataset_id}): ({len(df)} rows, {len(df.columns)} cols)") + else: + print(f"{name}: ({len(df)} rows, {len(df.columns)} cols)") + def summary(self, table_name=None): """ Returns numerical metadata for tables. + For resource tables, includes dataset_id information. + + Parameters + ---------- `table_name` : str, optional - If provided → returns summary for a single table - If None → returns summary for all tables as one DataFrame + If provided, returns summary for a single table. + Can be either dataset_title or dataset_id. + If None, returns summary for all tables as one DataFrame. + + Returns + ------- + pandas.DataFrame + Summary information including: + - table_name + - dataset_id (for resource tables) + - num_rows + - num_columns + - columns """ if not self._loaded: @@ -570,6 +966,10 @@ def summary(self, table_name=None): summaries = [] + if table_name: + resolved_name = self._resolve_table_name(table_name) + table_name = resolved_name + for name, table in self._cache.items(): if table_name and name != table_name: @@ -577,60 +977,88 @@ def summary(self, table_name=None): df = pd.DataFrame(table) - summaries.append({ + summary_dict = { "table_name": name, "num_rows": len(df), "num_columns": len(df.columns), "columns": list(df.columns) - }) + } + + if name in self._resource_tables: + summary_dict["dataset_id"] = self._dataset_title_map.get(name, "N/A") + + summaries.append(summary_dict) return pd.DataFrame(summaries) - - + + def display(self, table_name, num_rows=25, display_cols=None): """ Displays rows from a specified table. - `table_name` : str - Name of the table. + Accepts either dataset_title or dataset_id for resource tables. + Parameters + ---------- + `table_name` : str + Title or ID of the table to display `num_rows` : int, default 25 - Number of rows to display. - + Number of rows to display `display_cols` : list of str, optional - Subset of columns to display. + Subset of columns to display - `return` : - pandas.DataFrame + Returns + ------- + pandas.DataFrame + Displayed table data """ - table = self._cache.get(table_name) + if not self._loaded: + raise RuntimeError("No data loaded. Cannot display empty backend.") + + resolved_name = self._resolve_table_name(table_name) + + table = self._cache.get(resolved_name) if not table: - raise ValueError(f"Table '{table_name}' not found") + raise ValueError(f"Table '{resolved_name}' is empty") df = pd.DataFrame(table) if display_cols: + missing_cols = set(display_cols) - set(df.columns) + if missing_cols: + raise ValueError( + f"Columns not found in '{resolved_name}': {missing_cols}\n" + f"Available columns: {list(df.columns)}" + ) df = df[display_cols] - result = df.head(num_rows) - print(result) - return result + if num_rows: + df = df.head(num_rows) + + return df def notebook(self, **kwargs): """ Notebook generation not supported for NDP backend. - `return` : None + Parameters + ---------- + `**kwargs` : dict + Additional keyword arguments (unused) + + Returns + ------- + None """ pass - # --------------------------------------------------- + # ---------------------------------------------------------------------- # Lifecycle - # --------------------------------------------------- + # ---------------------------------------------------------------------- def close(self): """ Resets backend state and clears all cached data. @@ -638,16 +1066,32 @@ def close(self): self._cache = OrderedDict() self._resource_tables = [] + self._dataset_id_map = {} + self._dataset_title_map = {} self._loaded = False - # --------------------------------------------------- + # ---------------------------------------------------------------------- # Abstract Methods - # --------------------------------------------------- + # ---------------------------------------------------------------------- def ingest_artifacts(self, artifacts, **kwargs) -> None: """ Ingest not supported for NDP backend (read-only). - `return` : None + Parameters + ---------- + `artifacts` : any + Artifacts to ingest (unused) + `**kwargs` : dict + Additional keyword arguments (unused) + + Returns + ------- + None + + Raises + ------ + NotImplementedError + Always raised as NDP backend is read-only """ raise NotImplementedError("NDP backend is read-only") \ No newline at end of file diff --git a/dsi/cli.py b/dsi/cli.py index b22e8131..2fa0ab0e 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -107,7 +107,11 @@ def startup(self, backend="sqlite"): if backend=="duckdb": self.t.load_module('backend','DuckDB','back-write', filename = self.db_path) self.name = "duckdb" - # TODO: Add NDP + elif backend.lower() == "ndp": + # NDP requires params dict for initialization + print("NDP backend requires configuration.") + print("Use Python API: t.load_module('backend', 'NDP', 'back-read', params={...})") + self.exit_cli([]) else: backend = "sqlite" self.t.load_module('backend','Sqlite','back-write', filename = self.db_path) diff --git a/dsi/core.py b/dsi/core.py index ab9fd6a7..92e074ff 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -1397,8 +1397,19 @@ def valid_backend(self, backend, parent_name): return True elif parent_name == "Webserver": if backend.__class__.__name__ == "NDP": - # TODO: Use URL validation to see if NDP goes down, then let it pass - return True + # NDP is valid if data is loaded and connection works + if not backend._loaded: + return False + + try: + backend.validate_connection() + return True + except (ConnectionError, RuntimeError) as e: + if self.debug_level != 0: + self.logger.warning( + f"NDP backend connection validation failed: {str(e)}" + ) + return False return False # Internal function that returns if a user can create a file/db in a specified location From ddb36475b82b9ddf106490b90d0465a0d8d441e0 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 23:14:18 -0600 Subject: [PATCH 058/109] feat: updated test ndp --- dsi/backends/tests/test_ndp.py | 239 +++++++++++++++++++++++++++++---- 1 file changed, 216 insertions(+), 23 deletions(-) diff --git a/dsi/backends/tests/test_ndp.py b/dsi/backends/tests/test_ndp.py index 524f9694..ebf28be8 100644 --- a/dsi/backends/tests/test_ndp.py +++ b/dsi/backends/tests/test_ndp.py @@ -2,6 +2,7 @@ from collections import OrderedDict from dsi.core import Terminal import pytest +import pandas as pd # ---------------------------- # 1) Test loading NDP backend with specific parameters @@ -33,7 +34,32 @@ def test_ndp_load_and_query(): # ---------------------------- -# 2) Test process_artifacts structure +# 2) Test validate_connection +# ---------------------------- +def test_ndp_validate_connection(): + """Test connection validation to CKAN API.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Should not raise an exception + assert backend.validate_connection() is True + + terminal.close() + + +# ---------------------------- +# 3) Test process_artifacts structure # ---------------------------- def test_ndp_process_structure(): """Test that processed artifacts have correct tiered structure.""" @@ -67,7 +93,41 @@ def test_ndp_process_structure(): # ---------------------------- -# 3) Test query_artifacts with pandas query +# 4) Test table name resolution (dataset_id vs dataset_title) +# ---------------------------- +def test_ndp_table_name_resolution(): + """Test that both dataset_id and dataset_title can be used to access tables.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate data", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Should have some resource tables + if backend._resource_tables: + # Get first resource table + dataset_title = backend._resource_tables[0] + dataset_id = backend._dataset_title_map.get(dataset_title) + + if dataset_id: + # Both should resolve to same table + resolved_by_title = backend._resolve_table_name(dataset_title) + resolved_by_id = backend._resolve_table_name(dataset_id) + assert resolved_by_title == resolved_by_id == dataset_title + + terminal.close() + + +# ---------------------------- +# 5) Test query_artifacts with pandas query # ---------------------------- def test_ndp_query_with_pandas(): """Test querying loaded data with pandas query string.""" @@ -91,13 +151,13 @@ def test_ndp_query_with_pandas(): assert isinstance(result, dict) # Should have results if any datasets have resources if result: - assert "datasets" in result or any("resources_" in key for key in result.keys()) + assert any(key in result for key in backend._cache.keys()) terminal.close() # ---------------------------- -# 4) Test invalid pandas query raises error +# 6) Test invalid pandas query raises error # ---------------------------- def test_ndp_query_invalid(): """Test that invalid pandas queries raise appropriate errors.""" @@ -122,7 +182,7 @@ def test_ndp_query_invalid(): # ---------------------------- -# 5) Test find methods +# 7) Test find methods # ---------------------------- def test_ndp_find_operations(): """Test find operations across tables, columns, and cells.""" @@ -159,7 +219,7 @@ def test_ndp_find_operations(): # ---------------------------- -# 6) Test combined find() +# 8) Test combined find() # ---------------------------- def test_ndp_find_combined(): """Test the general find() method that searches everywhere.""" @@ -184,7 +244,33 @@ def test_ndp_find_combined(): # ---------------------------- -# 7) Test URL validation on resource tables +# 9) Test find_relation (should return empty list) +# ---------------------------- +def test_ndp_find_relation(): + """Test that find_relation returns empty list (not supported).""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + result = backend.find_relation("column_name", "= 'value'") + assert isinstance(result, list) + assert len(result) == 0 + + terminal.close() + + +# ---------------------------- +# 10) Test URL validation on resource tables # ---------------------------- def test_ndp_validate_urls(): """Test URL validation for dataset resources.""" @@ -217,10 +303,10 @@ def test_ndp_validate_urls(): # ---------------------------- -# 8) Test list and summary methods +# 11) Test list method # ---------------------------- -def test_ndp_list_and_summary(): - """Test metadata retrieval methods.""" +def test_ndp_list(): + """Test list method returns table names.""" terminal = Terminal() terminal.load_module( @@ -236,23 +322,105 @@ def test_ndp_list_and_summary(): backend = terminal.active_modules["back-read"][0] # Test list with collection=True - table_info = backend.list(collection=True) - assert isinstance(table_info, dict) - assert "datasets" in table_info - assert "resources" in table_info - assert isinstance(table_info["resources"], list) + table_names = backend.list(collection=True) + assert isinstance(table_names, (dict_keys, list)) + assert "datasets" in table_names + + terminal.close() + + +# ---------------------------- +# 12) Test summary method +# ---------------------------- +def test_ndp_summary(): + """Test summary returns DataFrame with table metadata.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] - # Test summary + # Test summary for all tables summary_df = backend.summary() + assert isinstance(summary_df, pd.DataFrame) assert not summary_df.empty assert "table_name" in summary_df.columns assert "num_rows" in summary_df.columns + # Test summary for specific table + summary_single = backend.summary("datasets") + assert isinstance(summary_single, pd.DataFrame) + + terminal.close() + + +# ---------------------------- +# 13) Test get_table method +# ---------------------------- +def test_ndp_get_table(): + """Test getting table data as DataFrame or OrderedDict.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + # Get as DataFrame + df = backend.get_table("datasets", dict_return=False) + assert isinstance(df, pd.DataFrame) + + # Get as OrderedDict + dict_data = backend.get_table("datasets", dict_return=True) + assert isinstance(dict_data, OrderedDict) + terminal.close() # ---------------------------- -# 9) Test read-only enforcement +# 14) Test get_schema method +# ---------------------------- +def test_ndp_get_schema(): + """Test that get_schema returns informative message.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + schema = backend.get_schema() + assert isinstance(schema, str) + assert "NDP" in schema + assert "read-only" in schema + + terminal.close() + + +# ---------------------------- +# 15) Test read-only enforcement # ---------------------------- def test_ndp_read_only(): """Test that NDP backend is properly read-only.""" @@ -277,7 +445,7 @@ def test_ndp_read_only(): # ---------------------------- -# 10) Test display method +# 16) Test display method # ---------------------------- def test_ndp_display(): """Test display method for tables.""" @@ -297,14 +465,14 @@ def test_ndp_display(): # Display datasets table result = backend.display("datasets", num_rows=10) - assert result is not None + assert isinstance(result, pd.DataFrame) assert len(result) <= 10 terminal.close() # ---------------------------- -# 11) Test with different organizations +# 17) Test with different organizations # ---------------------------- def test_ndp_organization_filtering(): """Test querying with organization filter.""" @@ -331,7 +499,7 @@ def test_ndp_organization_filtering(): # ---------------------------- -# 12) Test with tags filter +# 18) Test with tags filter # ---------------------------- def test_ndp_tags_filtering(): """Test querying with tags filter.""" @@ -358,7 +526,7 @@ def test_ndp_tags_filtering(): # ---------------------------- -# 13) Test with format filter +# 19) Test with format filter # ---------------------------- def test_ndp_format_filtering(): """Test querying with resource format filter.""" @@ -383,7 +551,7 @@ def test_ndp_format_filtering(): # ---------------------------- -# 14) Test close method +# 20) Test close method # ---------------------------- def test_ndp_close(): """Test that close() properly resets backend state.""" @@ -409,4 +577,29 @@ def test_ndp_close(): assert len(backend._cache) == 0 assert len(backend._resource_tables) == 0 + terminal.close() + + +# ---------------------------- +# 21) Test overwrite_table raises NotImplementedError +# ---------------------------- +def test_ndp_overwrite_table(): + """Test that overwrite_table raises NotImplementedError.""" + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "limit": 5 + } + ) + + backend = terminal.active_modules["back-read"][0] + + with pytest.raises(NotImplementedError): + backend.overwrite_table("datasets", pd.DataFrame()) + terminal.close() \ No newline at end of file From 1df112b6da387ce761f37834e661e229731bb8d0 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 23:16:35 -0600 Subject: [PATCH 059/109] fix: linter changes --- dsi/backends/ndp.py | 2 +- dsi/backends/tests/test_ndp.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 6e929484..5c9d447a 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -282,7 +282,7 @@ def _resolve_table_name(self, identifier): error_msg = f"Table '{identifier}' not found.\n" if available_titles: - error_msg += f"\nAvailable dataset titles:\n" + error_msg += "\nAvailable dataset titles:\n" for title in available_titles[:5]: dataset_id = self._dataset_title_map.get(title, "N/A") error_msg += f" - '{title}' (ID: {dataset_id})\n" diff --git a/dsi/backends/tests/test_ndp.py b/dsi/backends/tests/test_ndp.py index ebf28be8..1747f57a 100644 --- a/dsi/backends/tests/test_ndp.py +++ b/dsi/backends/tests/test_ndp.py @@ -323,7 +323,6 @@ def test_ndp_list(): # Test list with collection=True table_names = backend.list(collection=True) - assert isinstance(table_names, (dict_keys, list)) assert "datasets" in table_names terminal.close() From 9387573047a64d51e55c6345ed11659538178992 Mon Sep 17 00:00:00 2001 From: krhom Date: Thu, 16 Apr 2026 23:36:36 -0600 Subject: [PATCH 060/109] feat: added tests updates --- dsi/backends/tests/test_ndp.py | 659 +++++++++------------ dsi/tests/test_core.py | 417 +++++++++++++ examples/ndp/ndp_developer/1.load_basic.py | 67 ++- 3 files changed, 743 insertions(+), 400 deletions(-) diff --git a/dsi/backends/tests/test_ndp.py b/dsi/backends/tests/test_ndp.py index 1747f57a..8e7a0b71 100644 --- a/dsi/backends/tests/test_ndp.py +++ b/dsi/backends/tests/test_ndp.py @@ -1,119 +1,86 @@ -# dsi/backends/tests/test_ndp.py +""" +NDP Backend Function Tests + +Tests NDP backend methods directly without Terminal integration. +Mirrors structure of test_sqlite.py +""" + from collections import OrderedDict -from dsi.core import Terminal import pytest import pandas as pd +from dsi.backends.ndp import NDP -# ---------------------------- -# 1) Test loading NDP backend with specific parameters -# ---------------------------- -def test_ndp_load_and_query(): - """Test NDP backend loads with specific climate data parameters.""" - terminal = Terminal() - - # Load NDP backend with climate search - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate temperature precipitation", - "organization": "noaa-gov", - "limit": 10 - } + +# ============================================================================= +# 1) Basic Backend Initialization +# ============================================================================= + +def test_ndp_initialization(): + """Test NDP backend initializes correctly.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - - # Verify data was loaded assert backend._loaded is True assert len(backend._cache) > 0 assert "datasets" in backend._cache - terminal.close() + backend.close() -# ---------------------------- -# 2) Test validate_connection -# ---------------------------- def test_ndp_validate_connection(): """Test connection validation to CKAN API.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - - # Should not raise an exception + # Should return True without raising exception assert backend.validate_connection() is True - terminal.close() + backend.close() -# ---------------------------- -# 3) Test process_artifacts structure -# ---------------------------- -def test_ndp_process_structure(): - """Test that processed artifacts have correct tiered structure.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", +def test_ndp_invalid_url(): + """Test that invalid URL raises appropriate error.""" + with pytest.raises(ValueError): + NDP(url="not-a-valid-url") + + +# ============================================================================= +# 2) Data Loading and Structure +# ============================================================================= + +def test_ndp_load_initial_data(): + """Test that initial data load creates proper structure.""" + backend = NDP( params={ "keywords": "ocean temperature", "limit": 5 } ) - backend = terminal.active_modules["back-read"][0] - artifacts = backend.process_artifacts() - - # Verify tiered structure - assert isinstance(artifacts, OrderedDict) - assert "datasets" in artifacts - assert isinstance(artifacts["datasets"], OrderedDict) + # Verify structure + assert isinstance(backend._cache, OrderedDict) + assert "datasets" in backend._cache + assert isinstance(backend._cache["datasets"], OrderedDict) # Check dataset table has expected columns - dataset_cols = list(artifacts["datasets"].keys()) + dataset_cols = list(backend._cache["datasets"].keys()) assert "id" in dataset_cols assert "title" in dataset_cols assert "num_resources" in dataset_cols - terminal.close() + backend.close() -# ---------------------------- -# 4) Test table name resolution (dataset_id vs dataset_title) -# ---------------------------- def test_ndp_table_name_resolution(): - """Test that both dataset_id and dataset_title can be used to access tables.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate data", - "limit": 5 - } + """Test that both dataset_id and dataset_title resolve correctly.""" + backend = NDP( + params={"keywords": "climate data", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - - # Should have some resource tables + # If we have resource tables, test resolution if backend._resource_tables: - # Get first resource table dataset_title = backend._resource_tables[0] dataset_id = backend._dataset_title_map.get(dataset_title) @@ -123,171 +90,165 @@ def test_ndp_table_name_resolution(): resolved_by_id = backend._resolve_table_name(dataset_id) assert resolved_by_title == resolved_by_id == dataset_title - terminal.close() + backend.close() + +# ============================================================================= +# 3) Query Operations +# ============================================================================= -# ---------------------------- -# 5) Test query_artifacts with pandas query -# ---------------------------- -def test_ndp_query_with_pandas(): +def test_ndp_query_artifacts(): """Test querying loaded data with pandas query string.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate data", - "limit": 10 - } + backend = NDP( + params={"keywords": "climate data", "limit": 10} ) - backend = terminal.active_modules["back-read"][0] - # Query datasets with resources result = backend.query_artifacts("num_resources > 0", dict_return=True) + # Assertions assert isinstance(result, dict) - # Should have results if any datasets have resources + + # If we got results, verify structure if result: - assert any(key in result for key in backend._cache.keys()) + for table_name, table_data in result.items(): + assert isinstance(table_data, dict) + # Each column should be a list + for col_values in table_data.values(): + assert isinstance(col_values, list) - terminal.close() + backend.close() -# ---------------------------- -# 6) Test invalid pandas query raises error -# ---------------------------- def test_ndp_query_invalid(): """Test that invalid pandas queries raise appropriate errors.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "temperature", - "limit": 5 - } + backend = NDP( + params={"keywords": "temperature", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - with pytest.raises(ValueError): backend.query_artifacts("INVALID SYNTAX ###") - terminal.close() + backend.close() -# ---------------------------- -# 7) Test find methods -# ---------------------------- -def test_ndp_find_operations(): - """Test find operations across tables, columns, and cells.""" - terminal = Terminal() +def test_ndp_get_table(): + """Test getting table data as DataFrame or OrderedDict.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} + ) - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate temperature", - "limit": 8 - } + # Get as DataFrame + df = backend.get_table("datasets", dict_return=False) + assert isinstance(df, pd.DataFrame) + assert not df.empty + + # Get as OrderedDict + dict_data = backend.get_table("datasets", dict_return=True) + assert isinstance(dict_data, OrderedDict) + assert len(dict_data) > 0 + + backend.close() + + +def test_ndp_get_schema(): + """Test that get_schema returns informative message.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] + schema = backend.get_schema() + assert isinstance(schema, str) + assert "NDP" in schema + assert "read-only" in schema.lower() + + backend.close() + + +# ============================================================================= +# 4) Find Operations +# ============================================================================= + +def test_ndp_find(): + """Test general find operation across all levels.""" + backend = NDP( + params={"keywords": "climate temperature", "limit": 8} + ) + + results = backend.find("title") + assert isinstance(results, list) + + backend.close() + + +def test_ndp_find_table(): + """Test finding tables by name.""" + backend = NDP( + params={"keywords": "climate temperature", "limit": 8} + ) - # Test finding tables tables_found = backend.find_table("datasets") + assert isinstance(tables_found, list) assert len(tables_found) > 0 assert any("datasets" in t.t_name for t in tables_found) - # Test finding columns + backend.close() + + +def test_ndp_find_column(): + """Test finding columns by name.""" + backend = NDP( + params={"keywords": "climate temperature", "limit": 8} + ) + columns_found = backend.find_column("title") + assert isinstance(columns_found, list) assert len(columns_found) > 0 assert any("title" in c.c_name for c in columns_found) - # Test finding cells - cells_found = backend.find_cell("climate") - if cells_found: - assert all(hasattr(cell, 'type') and cell.type == "cell" for cell in cells_found) - - terminal.close() + backend.close() -# ---------------------------- -# 8) Test combined find() -# ---------------------------- -def test_ndp_find_combined(): - """Test the general find() method that searches everywhere.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "ocean data", - "limit": 7 - } +def test_ndp_find_cell(): + """Test finding cells by value.""" + backend = NDP( + params={"keywords": "climate temperature", "limit": 8} ) - backend = terminal.active_modules["back-read"][0] - - results = backend.find("title") - assert isinstance(results, list) + cells_found = backend.find_cell("climate") + assert isinstance(cells_found, list) + if cells_found: # May be empty + assert all(hasattr(cell, 'type') and cell.type == "cell" for cell in cells_found) - terminal.close() + backend.close() -# ---------------------------- -# 9) Test find_relation (should return empty list) -# ---------------------------- def test_ndp_find_relation(): """Test that find_relation returns empty list (not supported).""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - result = backend.find_relation("column_name", "= 'value'") assert isinstance(result, list) - assert len(result) == 0 + assert len(result) == 0 # NDP doesn't support relational queries - terminal.close() + backend.close() + +# ============================================================================= +# 5) URL Validation +# ============================================================================= -# ---------------------------- -# 10) Test URL validation on resource tables -# ---------------------------- def test_ndp_validate_urls(): """Test URL validation for dataset resources.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", + backend = NDP( params={ - "keywords": "climate precipitation", - "organization": "noaa-gov", + "keywords": "climate", "limit": 5 } ) - backend = terminal.active_modules["back-read"][0] backend.validate_urls() # Check that url_valid was added to resource tables @@ -295,58 +256,50 @@ def test_ndp_validate_urls(): if table_name in backend._cache: table = backend._cache[table_name] if "url" in table and len(table["url"]) > 0: + # url_valid should exist and be boolean list assert "url_valid" in table assert all(isinstance(v, bool) for v in table["url_valid"]) assert len(table["url_valid"]) == len(table["url"]) - terminal.close() + backend.close() + +# ============================================================================= +# 6) List and Summary +# ============================================================================= -# ---------------------------- -# 11) Test list method -# ---------------------------- def test_ndp_list(): """Test list method returns table names.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "atmospheric pressure", - "limit": 6 - } + backend = NDP( + params={"keywords": "climate", "limit": 6} ) - backend = terminal.active_modules["back-read"][0] - # Test list with collection=True table_names = backend.list(collection=True) assert "datasets" in table_names + assert isinstance(table_names, (list, dict, type({}.keys()))) - terminal.close() + backend.close() + + +def test_ndp_num_tables(): + """Test num_tables prints correct count.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} + ) + + # Should not raise exception + backend.num_tables() + + backend.close() -# ---------------------------- -# 12) Test summary method -# ---------------------------- def test_ndp_summary(): """Test summary returns DataFrame with table metadata.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - # Test summary for all tables summary_df = backend.summary() assert isinstance(summary_df, pd.DataFrame) @@ -358,156 +311,111 @@ def test_ndp_summary(): summary_single = backend.summary("datasets") assert isinstance(summary_single, pd.DataFrame) - terminal.close() + backend.close() -# ---------------------------- -# 13) Test get_table method -# ---------------------------- -def test_ndp_get_table(): - """Test getting table data as DataFrame or OrderedDict.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } +def test_ndp_display(): + """Test display method for tables.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - - # Get as DataFrame - df = backend.get_table("datasets", dict_return=False) - assert isinstance(df, pd.DataFrame) - - # Get as OrderedDict - dict_data = backend.get_table("datasets", dict_return=True) - assert isinstance(dict_data, OrderedDict) + # Display datasets table + result = backend.display("datasets", num_rows=10) + assert isinstance(result, pd.DataFrame) + assert len(result) <= 10 - terminal.close() + backend.close() -# ---------------------------- -# 14) Test get_schema method -# ---------------------------- -def test_ndp_get_schema(): - """Test that get_schema returns informative message.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } +# ============================================================================= +# 7) Process Artifacts +# ============================================================================= + +def test_ndp_process_artifacts(): + """Test that processed artifacts have correct structure.""" + backend = NDP( + params={"keywords": "ocean temperature", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] + artifacts = backend.process_artifacts() - schema = backend.get_schema() - assert isinstance(schema, str) - assert "NDP" in schema - assert "read-only" in schema + # Verify structure + assert isinstance(artifacts, OrderedDict) + assert "datasets" in artifacts + assert isinstance(artifacts["datasets"], OrderedDict) - terminal.close() + backend.close() -# ---------------------------- -# 15) Test read-only enforcement -# ---------------------------- -def test_ndp_read_only(): - """Test that NDP backend is properly read-only.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "data", - "limit": 3 - } +# ============================================================================= +# 8) Read-Only Enforcement +# ============================================================================= + +def test_ndp_ingest_artifacts(): + """Test that ingest_artifacts raises NotImplementedError.""" + backend = NDP( + params={"keywords": "data", "limit": 3} ) - backend = terminal.active_modules["back-read"][0] - with pytest.raises(NotImplementedError): backend.ingest_artifacts({}) - terminal.close() + backend.close() -# ---------------------------- -# 16) Test display method -# ---------------------------- -def test_ndp_display(): - """Test display method for tables.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate ocean", - "limit": 5 - } +def test_ndp_overwrite_table(): + """Test that overwrite_table raises NotImplementedError.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] - - # Display datasets table - result = backend.display("datasets", num_rows=10) - assert isinstance(result, pd.DataFrame) - assert len(result) <= 10 + with pytest.raises(NotImplementedError): + backend.overwrite_table("datasets", pd.DataFrame()) - terminal.close() + backend.close() -# ---------------------------- -# 17) Test with different organizations -# ---------------------------- -def test_ndp_organization_filtering(): - """Test querying with organization filter.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", +# ============================================================================= +# 9) Filtering Tests +# ============================================================================= + +def test_ndp_organization_filter(): + """Test loading data with organization filter.""" + backend = NDP( params={ "keywords": "climate", - "organization": "noaa-gov", - "limit": 5 + "organization": "california-landscape-metrics", + "limit": 10 } ) - backend = terminal.active_modules["back-read"][0] data = backend.process_artifacts() assert "datasets" in data - assert len(data["datasets"]) > 0 + assert isinstance(data["datasets"], OrderedDict) - terminal.close() + # Check we got data (number of rows) + if data["datasets"]: + first_col = next(iter(data["datasets"].values())) + num_rows = len(first_col) + + # Verify organization column + assert "organization" in data["datasets"] + + orgs = data["datasets"]["organization"] + # Check at least one is from California Landscape Metrics + assert any( + org and "California Landscape Metrics" in org + for org in orgs + ) + + backend.close() -# ---------------------------- -# 18) Test with tags filter -# ---------------------------- -def test_ndp_tags_filtering(): - """Test querying with tags filter.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", +def test_ndp_tags_filter(): + """Test loading data with tags filter.""" + backend = NDP( params={ "keywords": "temperature", "tags": ["climate", "weather"], @@ -515,26 +423,16 @@ def test_ndp_tags_filtering(): } ) - backend = terminal.active_modules["back-read"][0] - assert backend._loaded is True data = backend.process_artifacts() assert "datasets" in data - terminal.close() + backend.close() -# ---------------------------- -# 19) Test with format filter -# ---------------------------- -def test_ndp_format_filtering(): - """Test querying with resource format filter.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", +def test_ndp_format_filter(): + """Test loading data with format filter.""" + backend = NDP( params={ "keywords": "data", "formats": ["CSV", "JSON"], @@ -542,63 +440,56 @@ def test_ndp_format_filtering(): } ) - backend = terminal.active_modules["back-read"][0] - assert backend._loaded is True + data = backend.process_artifacts() + assert isinstance(data, OrderedDict) - terminal.close() + backend.close() -# ---------------------------- -# 20) Test close method -# ---------------------------- +# ============================================================================= +# 10) Lifecycle +# ============================================================================= + def test_ndp_close(): """Test that close() properly resets backend state.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] + # Before close assert backend._loaded is True assert len(backend._cache) > 0 + # Close backend.close() + # After close assert backend._loaded is False assert len(backend._cache) == 0 assert len(backend._resource_tables) == 0 - - terminal.close() -# ---------------------------- -# 21) Test overwrite_table raises NotImplementedError -# ---------------------------- -def test_ndp_overwrite_table(): - """Test that overwrite_table raises NotImplementedError.""" - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={ - "keywords": "climate", - "limit": 5 - } +def test_ndp_notebook(): + """Test that notebook() doesn't raise errors.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} ) - backend = terminal.active_modules["back-read"][0] + # Should not raise exception (even though it's a no-op) + backend.notebook() - with pytest.raises(NotImplementedError): - backend.overwrite_table("datasets", pd.DataFrame()) + backend.close() + + +def test_ndp_get_table_names(): + """Test extracting table names from query strings.""" + backend = NDP( + params={"keywords": "climate", "limit": 5} + ) + + # Test with datasets table + names = backend.get_table_names("SELECT * FROM datasets WHERE title LIKE '%climate%'") + assert "datasets" in names - terminal.close() \ No newline at end of file + backend.close() \ No newline at end of file diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index eb366857..2b5af376 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -1029,3 +1029,420 @@ def test_test_sanitize_input(): """) assert output == expected_output + +# ============================================================================= +# NDP BACKEND TESTS +# ============================================================================= + +def test_terminal_load_ndp(): + """Test Terminal can load NDP backend successfully.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + backend = a.active_modules["back-read"][0] + assert backend is not None + assert backend._loaded is True + + a.close() + + +def test_terminal_unload_ndp(): + """Test Terminal can unload NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + a.unload_module("backend", "NDP", "back-read") + assert len(a.active_modules["back-read"]) == 0 + + a.close() + + +def test_terminal_process_ndp(): + """Test Terminal can process artifacts from NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + a.artifact_handler(interaction_type="process") + + # Verify data is in active_metadata + assert len(a.active_metadata) > 0 + assert "datasets" in a.active_metadata + assert isinstance(a.active_metadata["datasets"], OrderedDict) + + a.close() + + +def test_terminal_query_ndp(): + """Test Terminal can query NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 10} + ) + + result = a.artifact_handler( + interaction_type="query", + query="num_resources > 0" + ) + + # Result should be dict by default for NDP + assert isinstance(result, dict) + + a.close() + + +def test_terminal_get_table_ndp(): + """Test Terminal.get_table() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + # Get as DataFrame + df = a.get_table("datasets", dict_return=False) + assert isinstance(df, pd.DataFrame) + + # Get as OrderedDict + dict_data = a.get_table("datasets", dict_return=True) + assert isinstance(dict_data, OrderedDict) + + a.close() + + +def test_terminal_list_ndp(): + """Test Terminal.list() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + # Should not raise exception + table_list = a.list(collection=True) + assert "datasets" in table_list + + a.close() + + +def test_terminal_num_tables_ndp(): + """Test Terminal.num_tables() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + # Should not raise exception + a.num_tables() + + a.close() + + +def test_terminal_summary_ndp(): + """Test Terminal.summary() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + # Get summary for all tables + summary_list = a.summary(collection=True) + assert isinstance(summary_list, list) + + # Get summary for specific table + summary_df = a.summary(table_name="datasets", collection=True) + assert isinstance(summary_df, pd.DataFrame) + + a.close() + + +def test_terminal_display_ndp(): + """Test Terminal.display() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + # Should not raise exception + f = io.StringIO() + with redirect_stdout(f): + a.display("datasets", num_rows=10) + output = f.getvalue() + + assert "datasets" in output + assert len(output) > 0 + + a.close() + + +def test_terminal_find_ndp(): + """Test Terminal.find() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + results = a.find("climate") + assert isinstance(results, list) + + a.close() + + +def test_terminal_find_table_ndp(): + """Test Terminal.find_table() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + results = a.find_table("datasets") + assert isinstance(results, list) + assert len(results) > 0 + + a.close() + + +def test_terminal_find_column_ndp(): + """Test Terminal.find_column() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + results = a.find_column("title") + assert isinstance(results, list) + + a.close() + + +def test_terminal_find_cell_ndp(): + """Test Terminal.find_cell() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + results = a.find_cell("climate") + assert isinstance(results, list) + + a.close() + + +def test_terminal_ingest_ndp_fails(): + """Test that Terminal.artifact_handler('ingest') fails with NDP.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + # Try to ingest - should fail because NDP is read-only + # and only loaded as back-read + with pytest.raises(NotImplementedError): + a.artifact_handler(interaction_type="ingest") + + a.close() + + +def test_terminal_overwrite_ndp_fails(): + """Test that Terminal.overwrite_table() fails with NDP.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + with pytest.raises(NotImplementedError): + a.overwrite_table("datasets", pd.DataFrame()) + + a.close() + + +def test_terminal_get_schema_ndp(): + """Test Terminal.get_schema() with NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + schema = a.get_schema() + assert isinstance(schema, str) + assert "NDP" in schema + + a.close() + + +def test_terminal_ndp_organization_filter(): + """Test Terminal loading NDP with organization filter.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "climate", + "organization": "california-landscape-metrics", + "limit": 10 + } + ) + + a.artifact_handler(interaction_type="process") + + assert "datasets" in a.active_metadata + assert isinstance(a.active_metadata["datasets"], OrderedDict) + + # Check we got data + if a.active_metadata["datasets"]: + first_col = next(iter(a.active_metadata["datasets"].values())) + num_rows = len(first_col) + + # Verify organization column exists + assert "organization" in a.active_metadata["datasets"] + + orgs = a.active_metadata["datasets"]["organization"] + # Check at least one is from California Landscape Metrics + assert any( + org and "California Landscape Metrics" in org + for org in orgs + ) + + a.close() + + +def test_terminal_ndp_tags_filter(): + """Test Terminal loading NDP with tags filter.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "temperature", + "tags": ["climate", "weather"], + "limit": 5 + } + ) + + backend = a.active_modules["back-read"][0] + assert backend._loaded is True + + a.artifact_handler(interaction_type="process") + assert "datasets" in a.active_metadata + + a.close() + + +def test_terminal_ndp_format_filter(): + """Test Terminal loading NDP with format filter.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={ + "keywords": "data", + "formats": ["CSV", "JSON"], + "limit": 10 + } + ) + + backend = a.active_modules["back-read"][0] + assert backend._loaded is True + + a.artifact_handler(interaction_type="process") + assert isinstance(a.active_metadata, OrderedDict) + + a.close() + + +def test_terminal_ndp_close(): + """Test that Terminal properly closes NDP backend.""" + a = Terminal() + + a.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + backend = a.active_modules["back-read"][0] + + # Before close + assert backend._loaded is True + assert len(backend._cache) > 0 + + # Close terminal (which should close backend) + a.close() + + # After close + assert backend._loaded is False + assert len(backend._cache) == 0 \ No newline at end of file diff --git a/examples/ndp/ndp_developer/1.load_basic.py b/examples/ndp/ndp_developer/1.load_basic.py index 5abcd9d1..d8f26123 100644 --- a/examples/ndp/ndp_developer/1.load_basic.py +++ b/examples/ndp/ndp_developer/1.load_basic.py @@ -1,22 +1,57 @@ -# examples/ndp/ndp_developer/1.load_basic.py +# # examples/ndp/ndp_developer/1.load_basic.py +# from dsi.core import Terminal + +# def main(verbose=False): +# terminal = Terminal() + +# # Load NDP backend (read-only) +# terminal.load_module("backend", "NDP", "back-read", params={"keywords": "climate", "limit": 15}) + +# backend = terminal.active_modules["back-read"][0] + +# if verbose: +# artifacts = backend.summary() +# print("Datasets loaded:") +# # print(artifacts) +# print(backend.display("datasets", num_rows=15, display_cols=["title", "organization", "num_resources"])) + + +# if __name__ == "__main__": +# import argparse +# parser = argparse.ArgumentParser() +# parser.add_argument("--verbose", action="store_true") +# args = parser.parse_args() +# main(verbose=args.verbose) + +# check_org_name.py from dsi.core import Terminal -def main(verbose=False): - terminal = Terminal() +terminal = Terminal() - # Load NDP backend (read-only) - terminal.load_module("backend", "NDP", "back-read", params={"keywords": "temperature", "limit": 5}) +# Load with just "climate" keyword and no org filter +terminal.load_module( + "backend", "NDP", "back-read", + params={"keywords": "climate", "limit": 50} +) - backend = terminal.active_modules["back-read"][0] +backend = terminal.active_modules["back-read"][0] +data = backend.process_artifacts() - if verbose: - artifacts = backend.summary() - print("Datasets loaded:") - print(artifacts) +# Get all unique organizations from datasets +if "datasets" in data and "organization" in data["datasets"]: + orgs = set(data["datasets"]["organization"]) + + print("Organizations found with 'climate' keyword:") + print("=" * 60) + for org in sorted(orgs): + if org: # Skip None values + print(f" - {org}") + + # Check if California Landscape Metrics is there + print("\n" + "=" * 60) + if any("California" in str(org) for org in orgs if org): + print("✓ Found California-related organization(s)") + else: + print("✗ No California organizations found with 'climate' keyword") -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file +terminal.close() \ No newline at end of file From 0567bfa80b123c5d59280ebcd96fd8b7d64e7231 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 00:52:23 -0600 Subject: [PATCH 061/109] fixed: improved tests and demo for dev --- dsi/backends/ndp.py | 72 ++++-- examples/ndp/ndp_developer/1.load_basic.py | 74 ++---- examples/ndp/ndp_developer/2.list_summary.py | 27 +- examples/ndp/ndp_developer/3.display.py | 37 +-- examples/ndp/ndp_developer/4.query.py | 35 ++- examples/ndp/ndp_developer/5.find.py | 36 +-- examples/ndp/ndp_developer/6.process.py | 22 +- examples/ndp/ndp_developer/7.validate.py | 36 +-- examples/ndp/ndp_developer/8.close.py | 24 +- examples/ndp/ndp_developer/ndp_demo.py | 251 ++++++++++++------- examples/ndp/ndp_user/ndp_demo.py | 178 ------------- 11 files changed, 336 insertions(+), 456 deletions(-) delete mode 100644 examples/ndp/ndp_user/ndp_demo.py diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 5c9d447a..03b6e647 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -948,48 +948,62 @@ def summary(self, table_name=None): `table_name` : str, optional If provided, returns summary for a single table. Can be either dataset_title or dataset_id. - If None, returns summary for all tables as one DataFrame. + If None, returns summary for all tables in expected format. Returns ------- - pandas.DataFrame - Summary information including: - - table_name - - dataset_id (for resource tables) - - num_rows - - num_columns - - columns + pandas.DataFrame or list + - If table_name is None: returns [table_names_list, df1, df2, ...] + - If table_name provided: returns single DataFrame """ if not self._loaded: return pd.DataFrame() - summaries = [] - if table_name: + # Single table - return DataFrame resolved_name = self._resolve_table_name(table_name) - table_name = resolved_name - + table = self._cache.get(resolved_name) + + if not table: + raise ValueError(f"Table '{resolved_name}' is empty") + + df = pd.DataFrame(table) + + summary_dict = { + "table_name": resolved_name, + "num_rows": len(df), + "num_columns": len(df.columns), + "columns": list(df.columns) + } + + if resolved_name in self._resource_tables: + summary_dict["dataset_id"] = self._dataset_title_map.get(resolved_name, "N/A") + + return pd.DataFrame([summary_dict]) + + # Multiple tables - return list format [table_names, df1, df2, ...] + table_names = [] + summary_dfs = [] + for name, table in self._cache.items(): - - if table_name and name != table_name: - continue - df = pd.DataFrame(table) - + summary_dict = { "table_name": name, "num_rows": len(df), "num_columns": len(df.columns), "columns": list(df.columns) } - + if name in self._resource_tables: summary_dict["dataset_id"] = self._dataset_title_map.get(name, "N/A") - - summaries.append(summary_dict) - - return pd.DataFrame(summaries) + + table_names.append(name) + summary_dfs.append(pd.DataFrame([summary_dict])) + + # Return as [table_names_list, df1, df2, ...] + return [table_names] + summary_dfs def display(self, table_name, num_rows=25, display_cols=None): @@ -1010,7 +1024,7 @@ def display(self, table_name, num_rows=25, display_cols=None): Returns ------- pandas.DataFrame - Displayed table data + Displayed table data with long strings truncated """ if not self._loaded: @@ -1034,11 +1048,18 @@ def display(self, table_name, num_rows=25, display_cols=None): ) df = df[display_cols] + # Set max_rows before limiting rows + df.attrs["max_rows"] = len(df) + if num_rows: df = df.head(num_rows) - return df + # Truncate long strings for display using df.map() (pandas 2.1.0+) + df = df.map( + lambda x: (str(x)[:60] + '...') if isinstance(x, str) and len(str(x)) > 60 else x + ) + return df def notebook(self, **kwargs): """ @@ -1094,4 +1115,5 @@ def ingest_artifacts(self, artifacts, **kwargs) -> None: NotImplementedError Always raised as NDP backend is read-only """ - raise NotImplementedError("NDP backend is read-only") \ No newline at end of file + raise NotImplementedError("NDP backend is read-only") + \ No newline at end of file diff --git a/examples/ndp/ndp_developer/1.load_basic.py b/examples/ndp/ndp_developer/1.load_basic.py index d8f26123..7ac4fadd 100644 --- a/examples/ndp/ndp_developer/1.load_basic.py +++ b/examples/ndp/ndp_developer/1.load_basic.py @@ -1,57 +1,25 @@ -# # examples/ndp/ndp_developer/1.load_basic.py -# from dsi.core import Terminal - -# def main(verbose=False): -# terminal = Terminal() - -# # Load NDP backend (read-only) -# terminal.load_module("backend", "NDP", "back-read", params={"keywords": "climate", "limit": 15}) - -# backend = terminal.active_modules["back-read"][0] - -# if verbose: -# artifacts = backend.summary() -# print("Datasets loaded:") -# # print(artifacts) -# print(backend.display("datasets", num_rows=15, display_cols=["title", "organization", "num_resources"])) - - -# if __name__ == "__main__": -# import argparse -# parser = argparse.ArgumentParser() -# parser.add_argument("--verbose", action="store_true") -# args = parser.parse_args() -# main(verbose=args.verbose) - -# check_org_name.py +# examples/ndp/ndp_developer/1.load_basic.py from dsi.core import Terminal -terminal = Terminal() - -# Load with just "climate" keyword and no org filter -terminal.load_module( - "backend", "NDP", "back-read", - params={"keywords": "climate", "limit": 50} -) - -backend = terminal.active_modules["back-read"][0] -data = backend.process_artifacts() - -# Get all unique organizations from datasets -if "datasets" in data and "organization" in data["datasets"]: - orgs = set(data["datasets"]["organization"]) +def main(verbose=False): + terminal = Terminal() - print("Organizations found with 'climate' keyword:") - print("=" * 60) - for org in sorted(orgs): - if org: # Skip None values - print(f" - {org}") + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "temperature", "limit": 5} + ) - # Check if California Landscape Metrics is there - print("\n" + "=" * 60) - if any("California" in str(org) for org in orgs if org): - print("✓ Found California-related organization(s)") - else: - print("✗ No California organizations found with 'climate' keyword") - -terminal.close() \ No newline at end of file + if verbose: + print("\nAvailable tables:") + terminal.list() + + terminal.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/2.list_summary.py b/examples/ndp/ndp_developer/2.list_summary.py index fcd8410a..db991099 100644 --- a/examples/ndp/ndp_developer/2.list_summary.py +++ b/examples/ndp/ndp_developer/2.list_summary.py @@ -1,5 +1,4 @@ # examples/ndp/ndp_developer/2.list_summary.py -import argparse from dsi.core import Terminal def main(verbose=False): @@ -9,27 +8,21 @@ def main(verbose=False): "backend", "NDP", "back-read", - params={"keywords": "energy", "limit": 10} + params={"keywords": "climate energy", "limit": 10} ) - backend = terminal.active_modules["back-read"][0] - if verbose: - print("\n=== List Tables ===") - backend.list(collection=False) # Prints table info - - print("\n=== Summary of Tables ===") - summary_df = backend.summary() - print(summary_df) - - # Get collection format - tables_dict = backend.list(collection=True) - print("\n=== Table Collections ===") - print(f"Dataset tables: {tables_dict['datasets']}") - print(f"Resource tables: {tables_dict['resources'][:3]}...") # Show first 3 + terminal.list() + print("\n") + terminal.num_tables() + print("\n") + terminal.summary() + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP list and summary example") + import argparse + parser = argparse.ArgumentParser() parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/3.display.py b/examples/ndp/ndp_developer/3.display.py index ef0ebbac..6aa2f16e 100644 --- a/examples/ndp/ndp_developer/3.display.py +++ b/examples/ndp/ndp_developer/3.display.py @@ -1,5 +1,4 @@ -# examples/ndp/ndp_developer/3.display.py -import argparse +# examples/ndp/ndp_developer/3.disply.py from dsi.core import Terminal def main(verbose=False): @@ -9,27 +8,33 @@ def main(verbose=False): "backend", "NDP", "back-read", - params={"keywords": "water", "limit": 8} + params={"keywords": "water quality", "limit": 3} ) - backend = terminal.active_modules["back-read"][0] - if verbose: - print("\n=== Display datasets table ===") - backend.display("datasets", num_rows=5) + terminal.display("datasets", num_rows=5) + + terminal.display( + "datasets", + num_rows=3, + display_cols=["title", "organization", "num_resources"] + ) - # Display specific columns - print("\n=== Display datasets (specific columns) ===") - backend.display("datasets", num_rows=3, display_cols=["title", "organization", "num_resources"]) + table_names = list(terminal.list(collection=True)) + resource_tables = [t for t in table_names if t != "datasets"] - # Display a resource table if available - if backend._resource_tables: - resource_table = backend._resource_tables[0] - print(f"\n=== Display {resource_table} ===") - backend.display(resource_table, num_rows=5, display_cols=["resource_name", "format", "url"]) + if resource_tables: + terminal.display( + resource_tables[0], + num_rows=5, + display_cols=["resource_name", "format", "url"] + ) + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP display example") + import argparse + parser = argparse.ArgumentParser() parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/4.query.py b/examples/ndp/ndp_developer/4.query.py index 5da28c82..fd1bb6fe 100644 --- a/examples/ndp/ndp_developer/4.query.py +++ b/examples/ndp/ndp_developer/4.query.py @@ -1,11 +1,11 @@ -# examples/ndp/ndp_developer4.query.py +# examples/ndp/ndp_developer/4.query.py import argparse from dsi.core import Terminal def main(verbose=False): terminal = Terminal() - # 1. Load NDP backend (ingest step) + # Load NDP backend (ingest step) terminal.load_module( "backend", "NDP", @@ -15,22 +15,35 @@ def main(verbose=False): backend = terminal.active_modules["back-read"][0] - # 2. Query step + # Query with dict_return=True returns OrderedDict result = backend.query_artifacts( - query="`num_resources` > 10", + query="`num_resources` > 5", dict_return=True ) - # 3. OUTPUT STEP if verbose: - print("\nQuery results (num_resources > 10):") - print(f"Tables returned: {len(result)}") - + print("\n=== Query Results (num_resources > 5) ===") + for table_name, table_data in result.items(): print(f"\n[{table_name}]") - - for col, values in table_data.items(): - print(f" {col}: {values}") + print(f" Columns: {list(table_data.keys())}") + + # Get number of rows + num_rows = len(list(table_data.values())[0]) if table_data else 0 + print(f" Total Rows: {num_rows}") + + # Print each row with its num_resources + if 'num_resources' in table_data and num_rows > 0: + print(f"\n Rows with resource counts:") + for i in range(num_rows): + title = table_data.get('title', ['N/A'] * num_rows)[i] + resources = table_data['num_resources'][i] + org = table_data.get('organization', ['N/A'] * num_rows)[i] + + # Truncate title if too long + title_display = title[:50] + '...' if len(str(title)) > 50 else title + + print(f" Row {i+1}: {resources} resources - {title_display} ({org})") if __name__ == "__main__": parser = argparse.ArgumentParser(description="NDP query example") diff --git a/examples/ndp/ndp_developer/5.find.py b/examples/ndp/ndp_developer/5.find.py index a702da9f..2b90cd10 100644 --- a/examples/ndp/ndp_developer/5.find.py +++ b/examples/ndp/ndp_developer/5.find.py @@ -1,41 +1,43 @@ # examples/ndp/ndp_developer/5.find.py -import argparse from dsi.core import Terminal def main(verbose=False): terminal = Terminal() - # Load NDP backend with initial data terminal.load_module( "backend", "NDP", "back-read", - params={"keywords": "climate", "limit": 10} + params={"keywords": "climate ocean", "limit": 10} ) backend = terminal.active_modules["back-read"][0] - - # Find methods work on the cached in-memory data + tables_found = backend.find_table("datasets") columns_found = backend.find_column("title") - cells_found = backend.find_cell("Canada") - + cells_found = backend.find_cell("climate") + if verbose: - print("\n=== Find Results ===") - print(f"\nTables matching 'datasets': {len(tables_found)}") + print(f"\n=== Tables matching 'datasets': {len(tables_found)} ===") for v in tables_found: - print(f" - {v.t_name} (columns: {', '.join(v.c_name[:3])}...)") + print(f" {v.t_name}") + print(f" Columns ({len(v.c_name)}): {', '.join(v.c_name[:5])}...") - print(f"\nColumns matching 'title': {len(columns_found)}") - for v in columns_found[:5]: # Show first 5 - print(f" - Table: {v.t_name}, Column: {v.c_name[0]}") + print(f"\n=== Columns matching 'title': {len(columns_found)} ===") + for v in columns_found[:3]: + print(f" {v.t_name}.{v.c_name[0]}") + print(f" Sample values: {v.value[:2]}") - print(f"\nCells matching 'Canada': {len(cells_found)}") - for v in cells_found[:5]: # Show first 5 - print(f" - Table: {v.t_name}, Column: {v.c_name[0]}, Row: {v.row_num}, Value: {v.value}") + print(f"\n=== Cells matching 'climate': {len(cells_found)} ===") + for v in cells_found[:5]: + preview = str(v.value)[:60] + "..." if len(str(v.value)) > 60 else str(v.value) + print(f" {v.t_name}.{v.c_name[0]}[{v.row_num}]: {preview}") + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP find example") + import argparse + parser = argparse.ArgumentParser() parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/6.process.py b/examples/ndp/ndp_developer/6.process.py index cbd81628..e71a295a 100644 --- a/examples/ndp/ndp_developer/6.process.py +++ b/examples/ndp/ndp_developer/6.process.py @@ -1,32 +1,30 @@ # examples/ndp/ndp_developer/6.process.py -import argparse from dsi.core import Terminal def main(verbose=False): terminal = Terminal() - - # Load NDP backend with initial query + terminal.load_module( "backend", "NDP", "back-read", params={"keywords": "climate", "limit": 10} ) - + backend = terminal.active_modules["back-read"][0] - - # Process artifacts returns the cached tables (datasets + resource tables) tables = backend.process_artifacts() - + if verbose: - print("\nProcessed artifacts (cached tables):\n") + print("\nProcessed tables:") for table_name in tables.keys(): - num_rows = len(tables[table_name].get(list(tables[table_name].keys())[0], [])) - num_cols = len(tables[table_name].keys()) - print(f" [{table_name}]: {num_rows} rows, {num_cols} columns") + first_col = list(tables[table_name].values())[0] + print(f" {table_name}: {len(first_col)} rows") + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP process example") + import argparse + parser = argparse.ArgumentParser() parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/7.validate.py b/examples/ndp/ndp_developer/7.validate.py index d702c57a..769cfe62 100644 --- a/examples/ndp/ndp_developer/7.validate.py +++ b/examples/ndp/ndp_developer/7.validate.py @@ -1,5 +1,4 @@ # examples/ndp/ndp_developer/7.validate.py -import argparse from dsi.core import Terminal def main(verbose=False): @@ -14,38 +13,27 @@ def main(verbose=False): backend = terminal.active_modules["back-read"][0] - # Process artifacts to get the tables - artifacts = backend.process_artifacts() - if verbose: - print("\n=== Before Validation ===") - print(f"Datasets loaded: {len(artifacts.get('datasets', {}).get('id', []))}") - - # Count total resources across all resource tables - total_resources = sum( - len(table.get('url', [])) - for name, table in artifacts.items() - if name.startswith('resources_') - ) - print(f"Total resources: {total_resources}") - - # Validate URLs (adds 'url_valid' column to resource tables) + print("\nValidating URLs...") + backend.validate_urls() - + if verbose: - print("\n=== After Validation ===") - for table_name in backend._resource_tables[:3]: # Show first 3 resource tables + artifacts = backend.process_artifacts() + for table_name in backend._resource_tables[:2]: table = artifacts.get(table_name, {}) urls = table.get("url", []) - url_valid = table.get("url_valid", []) + valid = table.get("url_valid", []) print(f"\n{table_name}:") - for i, (url, valid) in enumerate(zip(urls[:3], url_valid[:3])): # First 3 URLs - status = "✓ Valid" if valid else "✗ Invalid" - print(f" {i+1}. {status}: {url[:60]}...") + print(f" Total URLs: {len(urls)}") + print(f" Valid: {sum(valid)}/{len(valid)}") + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP URL validation example") + import argparse + parser = argparse.ArgumentParser() parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/8.close.py b/examples/ndp/ndp_developer/8.close.py index c68fb385..e536a46e 100644 --- a/examples/ndp/ndp_developer/8.close.py +++ b/examples/ndp/ndp_developer/8.close.py @@ -1,5 +1,4 @@ # examples/ndp/ndp_developer/8.close.py -import argparse from dsi.core import Terminal def main(verbose=False): @@ -13,25 +12,20 @@ def main(verbose=False): ) backend = terminal.active_modules["back-read"][0] - + if verbose: - print("\n=== Before close ===") - print(f"Loaded: {backend._loaded}") - print(f"Cached tables: {list(backend._cache.keys())}") - summary = backend.summary() - print(f"Number of tables: {len(summary)}") - - # Close resets the backend state + print(f"\nBefore close: {len(backend._cache)} tables") + backend.close() - + if verbose: - print("\n=== After close ===") - print(f"Loaded: {backend._loaded}") - print(f"Cached tables: {list(backend._cache.keys())}") - print(f"Resource tables: {backend._resource_tables}") + print(f"After close: {len(backend._cache)} tables") + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP close/reset example") + import argparse + parser = argparse.ArgumentParser() parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_developer/ndp_demo.py b/examples/ndp/ndp_developer/ndp_demo.py index 3be8f4e9..2a12ad2b 100644 --- a/examples/ndp/ndp_developer/ndp_demo.py +++ b/examples/ndp/ndp_developer/ndp_demo.py @@ -11,86 +11,99 @@ def demo_ndp(verbose=True): # 1) Initialize backend # ---------------------------- t = Terminal() - t.load_module("backend", "NDP", "back-read") + t.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 20} + ) backend = t.active_modules["back-read"][0] # ---------------------------- - # 2) First search + # 2) First search - Process artifacts # ---------------------------- - keyword = "wildfire" + keyword = "climate" print(f"\n--- Searching NDP for '{keyword}' datasets ---") - backend.query_artifacts( - query=None, - kwargs={"keywords": keyword, "limit": 20} - ) - artifacts = backend.process_artifacts() df_ds = pd.DataFrame(artifacts.get("datasets", {})) - df_res = pd.DataFrame(artifacts.get("resources", {})) + + # Count total resources across all resource tables + total_resources = sum( + len(table.get('url', [])) + for name, table in artifacts.items() + if name != "datasets" + ) print(f"Total datasets: {len(df_ds)}") - print(f"Total resources: {len(df_res)}") + print(f"Total resources: {total_resources}") + print(f"Resource tables: {len(backend._resource_tables)}") - # ---------------------------- - # ✅ TEST 1 - # ---------------------------- assert len(df_ds) > 0, "No datasets returned" # ---------------------------- # 3) Print organizations # ---------------------------- print("\n--- Organizations ---") - orgs = df_ds["organization"].dropna().unique() - for o in orgs: - print(f"- {o}") + if "organization" in df_ds.columns: + orgs = df_ds["organization"].dropna().unique() + for o in orgs[:10]: # Show first 10 + print(f"- {o}") # ---------------------------- - # 4) Print name + # 4) Print titles # ---------------------------- - print("\n--- Titles ---") - titles = df_ds["title"].dropna().unique() - for t in titles: - print(f"- {t}") + print("\n--- Titles (first 10) ---") + if "title" in df_ds.columns: + titles = df_ds["title"].dropna().unique() + for t in titles[:10]: + print(f"- {t}") # ---------------------------- # 5) Print tags # ---------------------------- - print("\n--- Tags ---") - tag_set = set() - for tgs in df_ds["tags"].dropna(): - for t in tgs.split(","): - tag_set.add(t.strip()) + print("\n--- Tags (first 20) ---") + if "tags" in df_ds.columns: + tag_set = set() + for tgs in df_ds["tags"].dropna(): + if isinstance(tgs, str): + for t in tgs.split(","): + tag_set.add(t.strip()) - for t in sorted(tag_set): - print(f"- {t}") + for t in sorted(list(tag_set)[:20]): + print(f"- {t}") # ---------------------------- # 6) Query in-memory # ---------------------------- print("\n--- Query: datasets with >5 resources ---") - q1 = backend.query_in_memory( - "`num_resources` > 5", - {"table": "datasets"} + q1_result = backend.query_artifacts( + query="`num_resources` > 5", + dict_return=True ) - df_q1 = pd.DataFrame(q1) - - print(df_q1[["title", "organization", "num_resources"]]) + + if "datasets" in q1_result: + df_q1 = pd.DataFrame(q1_result["datasets"]) + print(f"\nFound {len(df_q1)} datasets with >5 resources") + + if not df_q1.empty and all(col in df_q1.columns for col in ["title", "organization", "num_resources"]): + print("\nFirst 5 results:") + for idx in range(min(5, len(df_q1))): + row = df_q1.iloc[idx] + title_display = row['title'][:50] + '...' if len(str(row['title'])) > 50 else row['title'] + print(f" {idx+1}. {row['num_resources']} resources - {title_display} ({row['organization']})") - # ---------------------------- - # ✅ TEST 2 - # ---------------------------- - assert "num_resources" in df_q1.columns + assert "datasets" in q1_result, "Query should return datasets table" # ---------------------------- - # 7) Select dataset + # 7) Select dataset for inspection # ---------------------------- print("\n--- Selecting dataset for deep inspection ---") if not df_ds.empty: - # Prefer datasets with fewer resources - filtered = df_ds[df_ds["num_resources"] < 10] + # Prefer datasets with moderate number of resources (2-10) + filtered = df_ds[(df_ds["num_resources"] >= 2) & (df_ds["num_resources"] <= 10)] if len(filtered) > 0: selected = filtered.iloc[0] @@ -99,29 +112,34 @@ def demo_ndp(verbose=True): ds_id = selected["id"] ds_title = selected["title"] + ds_num_resources = selected["num_resources"] print("Selected dataset:") print(f" ID: {ds_id}") print(f" Title: {ds_title}") - + print(f" Resources: {ds_num_resources}") else: print("No dataset available to select.") return # ---------------------------- - # 8) Re-query that dataset + # 8) Inspect selected dataset's resources # ---------------------------- - print("\n--- Re-querying selected dataset ---") + print("\n--- Inspecting selected dataset's resources ---") - backend.query_artifacts( - query=None, - kwargs={"keywords": ds_id, "limit": 1} - ) - - artifacts2 = backend.process_artifacts() - df_res2 = pd.DataFrame(artifacts2.get("resources", {})) - - print(f"Resources in selected dataset: {len(df_res2)}") + # The resource table name is the dataset title + if ds_title in artifacts: + df_res = pd.DataFrame(artifacts[ds_title]) + print(f"\nResources in '{ds_title}': {len(df_res)}") + + if not df_res.empty and all(col in df_res.columns for col in ["resource_name", "format", "url"]): + print("\nResource details (first 3):") + for idx in range(min(3, len(df_res))): + row = df_res.iloc[idx] + print(f" {idx+1}. {row['resource_name']} ({row['format']})") + print(f" URL: {row['url'][:70]}...") + else: + print(f"No resources found for dataset '{ds_title}'") # ---------------------------- # 9) Validate URLs @@ -129,47 +147,104 @@ def demo_ndp(verbose=True): print("\n--- Validating resource URLs ---") backend.validate_urls() - validated = backend.process_artifacts().get("resources", {}) - df_valid = pd.DataFrame(validated) - - if not df_valid.empty: - print(df_valid[["resource_name", "format", "url", "url_valid"]]) - - # ---------------------------- - # ✅ TEST 3 - # ---------------------------- - assert "url_valid" in df_valid.columns - - # ---------------------------- - # 10) Query valid URLs + validated = backend.process_artifacts() + + # Show validation results for selected dataset + if ds_title in validated: + df_valid = pd.DataFrame(validated[ds_title]) + + if "url_valid" in df_valid.columns: + valid_count = df_valid["url_valid"].sum() + total_count = len(df_valid) + print(f"\nValidation results for '{ds_title}':") + print(f" Valid URLs: {valid_count}/{total_count}") + + if not df_valid.empty: + print("\nFirst 3 resources:") + for idx in range(min(3, len(df_valid))): + row = df_valid.iloc[idx] + status = "✓ Valid" if row.get("url_valid", False) else "✗ Invalid" + print(f" {status} - {row.get('resource_name', 'N/A')} ({row.get('format', 'N/A')})") + + assert ds_title in validated, "Selected dataset should exist in validated artifacts" + if ds_title in validated: + assert "url_valid" in validated[ds_title], "url_valid column should exist" + + # ---------------------------- + # 10) Query valid URLs only # ---------------------------- print("\n--- Query: valid URLs only ---") - valid_only = backend.query_in_memory( - "url_valid == True", - {"table": "resources"} - ) - df_valid_only = pd.DataFrame(valid_only) - - print(df_valid_only[["resource_name", "url"]].head()) - - # ---------------------------- - # 11) Simulated download - # ---------------------------- - print("\n--- Simulated download ---") - - if not df_valid_only.empty: - sample_url = df_valid_only.iloc[0]["url"] - print(f"Example downloadable resource:\n{sample_url}") - - # ---------------------------- - # 12) Final artifact snapshot + try: + valid_only_result = backend.query_artifacts( + query="url_valid == True", + dict_return=True + ) + + valid_tables_count = len([t for t in valid_only_result.keys() if t != "datasets"]) + print(f"Found {valid_tables_count} resource tables with valid URLs") + + # Show first valid resource from first table + if ds_title in valid_only_result: + df_valid_only = pd.DataFrame(valid_only_result[ds_title]) + print(f"\nValid URLs in '{ds_title}': {len(df_valid_only)}") + + if not df_valid_only.empty and "resource_name" in df_valid_only.columns: + print("\nFirst 3 valid resources:") + for idx in range(min(3, len(df_valid_only))): + row = df_valid_only.iloc[idx] + print(f" {idx+1}. {row['resource_name']}") + if "url" in row: + print(f" {row['url'][:70]}...") + except ValueError as e: + print(f"Note: {e}") + + # ---------------------------- + # 11) Example downloadable resource + # ---------------------------- + print("\n--- Example downloadable resource ---") + + if ds_title in validated: + table_data = validated[ds_title] + if "url_valid" in table_data and "url" in table_data: + valid_indices = [i for i, v in enumerate(table_data["url_valid"]) if v] + if valid_indices: + sample_idx = valid_indices[0] + sample_url = table_data["url"][sample_idx] + sample_name = table_data.get("resource_name", ["Unknown"])[sample_idx] + print(f"Example valid resource: {sample_name}") + print(f"URL: {sample_url}") + + # ---------------------------- + # 12) Find operations + # ---------------------------- + print("\n--- Find operations ---") + + # Find tables + tables_found = backend.find_table("datasets") + print(f"\nTables matching 'datasets': {len(tables_found)}") + + # Find columns + columns_found = backend.find_column("title") + print(f"Columns matching 'title': {len(columns_found)}") + + # Find cells + cells_found = backend.find_cell(ds_title[:10] if len(ds_title) > 10 else ds_title) + print(f"Cells containing '{ds_title[:10]}': {len(cells_found)}") + + # ---------------------------- + # 13) Final artifact snapshot # ---------------------------- print("\n--- Final artifacts ---") - final_artifacts = backend.get_artifacts() - print("Datasets columns:", list(final_artifacts["datasets"].keys())) - print("Resources columns:", list(final_artifacts["resources"].keys())) + final_artifacts = backend.process_artifacts() + print(f"Total tables: {len(final_artifacts)}") + + datasets_count = len(final_artifacts.get('datasets', {}).get('id', [])) + print(f" - datasets: {datasets_count} rows") + + resource_tables = [k for k in final_artifacts.keys() if k != "datasets"] + print(f" - resource tables: {len(resource_tables)}") print("\n=== Demo complete ===") diff --git a/examples/ndp/ndp_user/ndp_demo.py b/examples/ndp/ndp_user/ndp_demo.py deleted file mode 100644 index 3be8f4e9..00000000 --- a/examples/ndp/ndp_user/ndp_demo.py +++ /dev/null @@ -1,178 +0,0 @@ -# examples/ndp/ndp_developer/ndp_demo.py - -from dsi.core import Terminal -import pandas as pd - - -def demo_ndp(verbose=True): - print("=== NDP Demo: Multi-Query + Validation + Inspection ===") - - # ---------------------------- - # 1) Initialize backend - # ---------------------------- - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - # ---------------------------- - # 2) First search - # ---------------------------- - keyword = "wildfire" - print(f"\n--- Searching NDP for '{keyword}' datasets ---") - - backend.query_artifacts( - query=None, - kwargs={"keywords": keyword, "limit": 20} - ) - - artifacts = backend.process_artifacts() - df_ds = pd.DataFrame(artifacts.get("datasets", {})) - df_res = pd.DataFrame(artifacts.get("resources", {})) - - print(f"Total datasets: {len(df_ds)}") - print(f"Total resources: {len(df_res)}") - - # ---------------------------- - # ✅ TEST 1 - # ---------------------------- - assert len(df_ds) > 0, "No datasets returned" - - # ---------------------------- - # 3) Print organizations - # ---------------------------- - print("\n--- Organizations ---") - orgs = df_ds["organization"].dropna().unique() - for o in orgs: - print(f"- {o}") - - # ---------------------------- - # 4) Print name - # ---------------------------- - print("\n--- Titles ---") - titles = df_ds["title"].dropna().unique() - for t in titles: - print(f"- {t}") - - # ---------------------------- - # 5) Print tags - # ---------------------------- - print("\n--- Tags ---") - tag_set = set() - for tgs in df_ds["tags"].dropna(): - for t in tgs.split(","): - tag_set.add(t.strip()) - - for t in sorted(tag_set): - print(f"- {t}") - - # ---------------------------- - # 6) Query in-memory - # ---------------------------- - print("\n--- Query: datasets with >5 resources ---") - - q1 = backend.query_in_memory( - "`num_resources` > 5", - {"table": "datasets"} - ) - df_q1 = pd.DataFrame(q1) - - print(df_q1[["title", "organization", "num_resources"]]) - - # ---------------------------- - # ✅ TEST 2 - # ---------------------------- - assert "num_resources" in df_q1.columns - - # ---------------------------- - # 7) Select dataset - # ---------------------------- - print("\n--- Selecting dataset for deep inspection ---") - - if not df_ds.empty: - # Prefer datasets with fewer resources - filtered = df_ds[df_ds["num_resources"] < 10] - - if len(filtered) > 0: - selected = filtered.iloc[0] - else: - selected = df_ds.iloc[0] - - ds_id = selected["id"] - ds_title = selected["title"] - - print("Selected dataset:") - print(f" ID: {ds_id}") - print(f" Title: {ds_title}") - - else: - print("No dataset available to select.") - return - - # ---------------------------- - # 8) Re-query that dataset - # ---------------------------- - print("\n--- Re-querying selected dataset ---") - - backend.query_artifacts( - query=None, - kwargs={"keywords": ds_id, "limit": 1} - ) - - artifacts2 = backend.process_artifacts() - df_res2 = pd.DataFrame(artifacts2.get("resources", {})) - - print(f"Resources in selected dataset: {len(df_res2)}") - - # ---------------------------- - # 9) Validate URLs - # ---------------------------- - print("\n--- Validating resource URLs ---") - - backend.validate_urls() - validated = backend.process_artifacts().get("resources", {}) - df_valid = pd.DataFrame(validated) - - if not df_valid.empty: - print(df_valid[["resource_name", "format", "url", "url_valid"]]) - - # ---------------------------- - # ✅ TEST 3 - # ---------------------------- - assert "url_valid" in df_valid.columns - - # ---------------------------- - # 10) Query valid URLs - # ---------------------------- - print("\n--- Query: valid URLs only ---") - - valid_only = backend.query_in_memory( - "url_valid == True", - {"table": "resources"} - ) - df_valid_only = pd.DataFrame(valid_only) - - print(df_valid_only[["resource_name", "url"]].head()) - - # ---------------------------- - # 11) Simulated download - # ---------------------------- - print("\n--- Simulated download ---") - - if not df_valid_only.empty: - sample_url = df_valid_only.iloc[0]["url"] - print(f"Example downloadable resource:\n{sample_url}") - - # ---------------------------- - # 12) Final artifact snapshot - # ---------------------------- - print("\n--- Final artifacts ---") - - final_artifacts = backend.get_artifacts() - print("Datasets columns:", list(final_artifacts["datasets"].keys())) - print("Resources columns:", list(final_artifacts["resources"].keys())) - - print("\n=== Demo complete ===") - - -if __name__ == "__main__": - demo_ndp() \ No newline at end of file From ce1aa43a683914e6b894c511d5dadc3bb4a4ae42 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 00:54:44 -0600 Subject: [PATCH 062/109] fix: test core library --- dsi/tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index 2b5af376..d47f4e85 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -4,7 +4,7 @@ import io from contextlib import redirect_stdout import textwrap - +import pandas as pd def test_terminal_module_getter(): a = Terminal() From eb7c55ec36bb4b74dd95b6a76c1f28dd8bcd70cc Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 00:59:53 -0600 Subject: [PATCH 063/109] fix: linter --- dsi/backends/tests/test_ndp.py | 3 --- dsi/tests/test_core.py | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dsi/backends/tests/test_ndp.py b/dsi/backends/tests/test_ndp.py index 8e7a0b71..6b5f0604 100644 --- a/dsi/backends/tests/test_ndp.py +++ b/dsi/backends/tests/test_ndp.py @@ -397,9 +397,6 @@ def test_ndp_organization_filter(): # Check we got data (number of rows) if data["datasets"]: - first_col = next(iter(data["datasets"].values())) - num_rows = len(first_col) - # Verify organization column assert "organization" in data["datasets"] diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index d47f4e85..eda001e8 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -5,6 +5,7 @@ from contextlib import redirect_stdout import textwrap import pandas as pd +import pytest def test_terminal_module_getter(): a = Terminal() From aa64ff42375f10015ae701ff34aa0fe7ca25ee7f Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 01:04:16 -0600 Subject: [PATCH 064/109] fix: linter --- dsi/tests/test_core.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index eda001e8..b7b72c95 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -1360,8 +1360,6 @@ def test_terminal_ndp_organization_filter(): # Check we got data if a.active_metadata["datasets"]: - first_col = next(iter(a.active_metadata["datasets"].values())) - num_rows = len(first_col) # Verify organization column exists assert "organization" in a.active_metadata["datasets"] From d97094682ce3eeb59555a007faa0e7e381eee836 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 01:13:09 -0600 Subject: [PATCH 065/109] fix: linter --- dsi/backends/tests/test_ndp.py | 22 ++++++++++++++-------- dsi/core.py | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dsi/backends/tests/test_ndp.py b/dsi/backends/tests/test_ndp.py index 6b5f0604..27959428 100644 --- a/dsi/backends/tests/test_ndp.py +++ b/dsi/backends/tests/test_ndp.py @@ -300,16 +300,22 @@ def test_ndp_summary(): params={"keywords": "climate", "limit": 5} ) - # Test summary for all tables - summary_df = backend.summary() - assert isinstance(summary_df, pd.DataFrame) - assert not summary_df.empty - assert "table_name" in summary_df.columns - assert "num_rows" in summary_df.columns - - # Test summary for specific table + # Test summary for all tables - returns list format + summary_list = backend.summary() + assert isinstance(summary_list, list) # ✅ Expect list + assert len(summary_list) > 1 # At least [table_names] + one df + + # First element should be table names list + assert isinstance(summary_list[0], list) + + # Remaining elements should be DataFrames + for df in summary_list[1:]: + assert isinstance(df, pd.DataFrame) + + # Test summary for specific table - returns DataFrame summary_single = backend.summary("datasets") assert isinstance(summary_single, pd.DataFrame) + assert "table_name" in summary_single.columns backend.close() diff --git a/dsi/core.py b/dsi/core.py index 92e074ff..11955748 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -730,7 +730,7 @@ def find_column(self, query_object, range = False): self.logger.error("Error in find column function: First loaded backend needs to have data to be able to find data from it") raise RuntimeError("Error in find column function: First loaded backend needs to have data to be able to find data from it") start = datetime.now() - return_object = backend.find_column(query_object, range) + return_object = backend.find_column(query_object, range=range) return self.find_helper(query_object, return_object, start, "column ") def find_cell(self, query_object, row = False): From fbec562e0aed73f78f0290e214e67f41804c56c9 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 01:19:26 -0600 Subject: [PATCH 066/109] refactor: add dsi errors --- dsi/core.py | 2 +- dsi/dsi.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dsi/core.py b/dsi/core.py index 11955748..3a55f5d7 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -765,7 +765,7 @@ def find_cell(self, query_object, row = False): self.logger.error("First loaded backend needs to have data to be able to find data from it") raise RuntimeError("First loaded backend needs to have data to be able to find data from it") start = datetime.now() - return_object = backend.find_cell(query_object, row) + return_object = backend.find_cell(query_object, row=row) return self.find_helper(query_object, return_object, start, "cell ") # Internal function to return found objects or print errors. diff --git a/dsi/dsi.py b/dsi/dsi.py index b955606a..2f12021a 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -166,6 +166,8 @@ def schema(self, filename = None): `return` : If filename = None, returns the structural schema of this database - table/col names and their units. **If loading a relational schema, this function must be called before reading in any associated data files** """ + if self.main_backend_obj.__class__.__name__ == "NDP": + raise RuntimeError("schema() ERROR: NDP is a read-only backend and does not support schema operations.") if filename: if not os.path.exists(filename): raise RuntimeError("schema() ERROR: Input schema file must have a valid filepath. Please check again.") @@ -265,6 +267,8 @@ def read(self, data_sources: str | list[str] | dict | pd.DataFrame | None = None Recommended when the input file contains a single table for the `CSV`, `Parquet`, `JSON`, or `Ensemble` reader. """ + if self.main_backend_obj.__class__.__name__ == "NDP": + raise RuntimeError("read() ERROR: NDP is a read-only backend. Data cannot be added.") # only DSI-repo readers require data_sources input. Custom readers do not. if isinstance(data_sources, str) and not os.path.exists(data_sources) and not reader_name.endswith(".py"): raise RuntimeError("read() ERROR: The input file must be a valid filepath. Please check again.") @@ -707,6 +711,9 @@ def update(self, collection, backup = False): - NOTE: Columns from the original table cannot be deleted during update. Only row edits or column additions are allowed. - NOTE: If update() affects a user-defined primary key column, row order may change upon reinsertion. """ + if self.main_backend_obj.__class__.__name__ == "NDP": + raise RuntimeError("update() ERROR: NDP is a read-only backend. Data cannot be updated.") + if not self.t.valid_backend(self.main_backend_obj, self.main_backend_obj.__class__.__bases__[0].__name__): raise RuntimeError("ERROR: Cannot update() an empty backend. Please ensure there is data in it.") if self.schema_read: From 2d02291cc9e9fdaa437c15f2d2b3f0d4f140ed8d Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 01:52:03 -0600 Subject: [PATCH 067/109] feat: adding user tests and dsi changes --- dsi/backends/ndp.py | 28 +++++-- dsi/dsi.py | 104 +++++++++++++++--------- examples/ndp/ndp_user/1.ingest.py | 23 ------ examples/ndp/ndp_user/1.load_basic.py | 28 +++++++ examples/ndp/ndp_user/2.list_summary.py | 28 +++++++ examples/ndp/ndp_user/2.process.py | 33 -------- examples/ndp/ndp_user/3.display.py | 40 +++++++++ examples/ndp/ndp_user/3.query.py | 50 ------------ examples/ndp/ndp_user/4.find.py | 25 ------ examples/ndp/ndp_user/4.query.py | 52 ++++++++++++ examples/ndp/ndp_user/5.find.py | 43 ++++++++++ examples/ndp/ndp_user/5.inspect.py | 25 ------ examples/ndp/ndp_user/6.process.py | 30 +++++++ examples/ndp/ndp_user/6.validate.py | 35 -------- examples/ndp/ndp_user/7.notebook.py | 19 ----- examples/ndp/ndp_user/7.validate.py | 39 +++++++++ examples/ndp/ndp_user/8.close.py | 32 +++++--- examples/ndp/ndp_user/9.run_all.py | 14 ++-- 18 files changed, 376 insertions(+), 272 deletions(-) delete mode 100644 examples/ndp/ndp_user/1.ingest.py create mode 100644 examples/ndp/ndp_user/1.load_basic.py create mode 100644 examples/ndp/ndp_user/2.list_summary.py delete mode 100644 examples/ndp/ndp_user/2.process.py create mode 100644 examples/ndp/ndp_user/3.display.py delete mode 100644 examples/ndp/ndp_user/3.query.py delete mode 100644 examples/ndp/ndp_user/4.find.py create mode 100644 examples/ndp/ndp_user/4.query.py create mode 100644 examples/ndp/ndp_user/5.find.py delete mode 100644 examples/ndp/ndp_user/5.inspect.py create mode 100644 examples/ndp/ndp_user/6.process.py delete mode 100644 examples/ndp/ndp_user/6.validate.py delete mode 100644 examples/ndp/ndp_user/7.notebook.py create mode 100644 examples/ndp/ndp_user/7.validate.py diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 03b6e647..e49d4f54 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -66,11 +66,11 @@ def __init__(self, url=None, params=None, **kwargs): `params` : dict, optional Dictionary of initial query parameters used to fetch data from CKAN. Supported keys: - - keywords - - organization - - tags - - formats - - limit + - keywords : str - Search keywords + - organization : str - Organization name filter + - tags : list - List of tags to filter by + - formats : list - List of resource formats (e.g., ['CSV', 'JSON']) + - limit : int - Maximum number of datasets to retrieve (default: 100) `**kwargs` : dict Additional keyword arguments: - api_key : str, optional @@ -109,9 +109,23 @@ def __init__(self, url=None, params=None, **kwargs): self._loaded = False self.params = params or {} - # Initial data load + # Validate connection FIRST before attempting to load data + try: + self.validate_connection() + except (ConnectionError, RuntimeError): + self._loaded = False + raise + + # Initial data load (only if connection is valid and params provided) if self.params: - self._load_initial_data(self.params) + try: + self._load_initial_data(self.params) + self._loaded = True # Data successfully loaded + except Exception as e: + self._loaded = False + raise RuntimeError(f"Failed to load initial data: {e}") from e + else: + self._loaded = True # Backend ready, no initial data to load # ---------------------------------------------------------------------- diff --git a/dsi/dsi.py b/dsi/dsi.py index 2f12021a..38aa1dec 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -36,11 +36,12 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) Accepted file extensions for DSI-compatible backends: - If backend_name = "Sqlite" → .db, .sqlite, .sqlite3 - If backend_name = "DuckDB" → .duckdb, .db + - If backend_name = "NDP" → No file required (read-only backend) `backend_name` : str, optional, default is "Sqlite". Name of the backend to activate. - If using a DSI-supported backend, must be either "Sqlite", "DuckDB". + If using a DSI-supported backend, must be either "Sqlite", "DuckDB", or "NDP". If using an external backend, provide the relative path to the Python module with the backend. """ @@ -52,15 +53,17 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) self.silence_messages = kwargs.pop('silence_messages', False) - if "/" in filename: - create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) - else: - create_bool = self.t.can_create_file_here() - if create_bool is False: - raise RuntimeError("Cannot initialize DSI due to write permissions in this directory. Please try elsewhere.") + # Skip file creation checks for NDP (read-only backend) + if backend_name.lower() != "ndp": + if "/" in filename: + create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) + else: + create_bool = self.t.can_create_file_here() + if create_bool is False: + raise RuntimeError("Cannot initialize DSI due to write permissions in this directory. Please try elsewhere.") - if filename == ".temp_dsi.db" and os.path.exists(filename): - os.remove(filename) + if filename == ".temp_dsi.db" and os.path.exists(filename): + os.remove(filename) if backend_name.endswith(".py"): if not os.path.exists(backend_name): @@ -108,37 +111,64 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) raise else: - if filename != ".temp_dsi.db" and backend_name.lower() == "sqlite": - file_extension = filename.rsplit(".", 1)[-1] if '.' in filename else '' - if file_extension.lower() not in ["db", "sqlite", "sqlite3"]: - filename += ".db" - elif filename != ".temp_dsi.db" and backend_name.lower() == "duckdb": - file_extension = filename.rsplit(".", 1)[-1] if '.' in filename else '' - if file_extension.lower() not in ["db", "duckdb"]: - filename += ".db" - self.database_name = filename - - correct_backend = True - try: - if backend_name.lower() == 'sqlite': - self.t.load_module('backend','Sqlite','back-write', filename=filename, **kwargs) - elif backend_name.lower() == 'duckdb': - self.t.load_module('backend','DuckDB','back-write', filename=filename, **kwargs) - else: - correct_backend = False - except Exception as e: - logger.error(f"backend ERROR: {e}", exc_info=True) - if e.args: - e.args = (f'backend ERROR: {str(e.args[0])}',) + e.args[1:] - raise - - if not correct_backend: - raise RuntimeError("Please check the 'backend_name' argument as that one is not supported by DSI\n" - "Eligible backend_names are: Sqlite, DuckDB") + # Handle NDP separately (read-only backend) + if backend_name.lower() == "ndp": + self.database_name = None # NDP doesn't use a file + + correct_backend = True + + # Extract NDP query parameters from kwargs + query_params = {} + ndp_param_keys = ['keywords', 'organization', 'tags', 'formats', 'limit'] + + for key in ndp_param_keys: + if key in kwargs: + query_params[key] = kwargs.pop(key) # Remove from kwargs after extraction + + try: + # Pass query params as 'params' argument + self.t.load_module('backend', 'NDP', 'back-read', params=query_params, **kwargs) + except Exception as e: + logger.error(f"backend ERROR: {e}", exc_info=True) + if e.args: + e.args = (f'backend ERROR: {str(e.args[0])}',) + e.args[1:] + raise + + # Handle file-based backends (Sqlite, DuckDB) + else: + if filename != ".temp_dsi.db" and backend_name.lower() == "sqlite": + file_extension = filename.rsplit(".", 1)[-1] if '.' in filename else '' + if file_extension.lower() not in ["db", "sqlite", "sqlite3"]: + filename += ".db" + elif filename != ".temp_dsi.db" and backend_name.lower() == "duckdb": + file_extension = filename.rsplit(".", 1)[-1] if '.' in filename else '' + if file_extension.lower() not in ["db", "duckdb"]: + filename += ".db" + self.database_name = filename + + correct_backend = True + try: + if backend_name.lower() == 'sqlite': + self.t.load_module('backend','Sqlite','back-write', filename=filename, **kwargs) + elif backend_name.lower() == 'duckdb': + self.t.load_module('backend','DuckDB','back-write', filename=filename, **kwargs) + else: + correct_backend = False + except Exception as e: + logger.error(f"backend ERROR: {e}", exc_info=True) + if e.args: + e.args = (f'backend ERROR: {str(e.args[0])}',) + e.args[1:] + raise + + if not correct_backend: + raise RuntimeError("Please check the 'backend_name' argument as that one is not supported by DSI\n" + "Eligible backend_names are: Sqlite, DuckDB, NDP") self.main_backend_obj = self.t.loaded_backends[0] - if filename != ".temp_dsi.db": + if backend_name.lower() == "ndp": + msg = f"Created an instance of DSI with the NDP read-only backend" + elif filename != ".temp_dsi.db": msg = f"Created an instance of DSI with the {backend_name} backend: {filename}" else: msg = "Created an instance of DSI" diff --git a/examples/ndp/ndp_user/1.ingest.py b/examples/ndp/ndp_user/1.ingest.py deleted file mode 100644 index 6971c060..00000000 --- a/examples/ndp/ndp_user/1.ingest.py +++ /dev/null @@ -1,23 +0,0 @@ -# examples/ndp/ndp_user/1.ingest.py -from dsi.dsi import DSI - -def main(): - # Initialize DSI (no file needed for web backend, but required arg) - dsi = DSI("ndp.db", webargs={ - "keywords": "data", - "limit": 5 - }) - - # Show tables - dsi.list() - - # Display datasets table - dsi.display("datasets") - - # Disable Read (but no error) - dsi.query("`num_resources` > 10", {"table": "datasets", "dict_return": True}) - - dsi.close() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/examples/ndp/ndp_user/1.load_basic.py b/examples/ndp/ndp_user/1.load_basic.py new file mode 100644 index 00000000..191c7e1f --- /dev/null +++ b/examples/ndp/ndp_user/1.load_basic.py @@ -0,0 +1,28 @@ +# examples/ndp/ndp_user/1.load_basic.py +from dsi.dsi import DSI + +def main(verbose=False): + dsi_instance = DSI( + backend_name="NDP", + keywords="temperature", + limit=5 + ) + + # ADD THESE DEBUG LINES: + print(f"\nDEBUG: Backend object: {dsi_instance.main_backend_obj}") + print(f"DEBUG: Backend loaded status: {dsi_instance.main_backend_obj._loaded}") + print(f"DEBUG: Cache keys: {list(dsi_instance.main_backend_obj._cache.keys())}") + print(f"DEBUG: Params used: {dsi_instance.main_backend_obj.params}") + + if verbose: + print("\nAvailable tables:") + dsi_instance.list() + + dsi_instance.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/2.list_summary.py b/examples/ndp/ndp_user/2.list_summary.py new file mode 100644 index 00000000..db991099 --- /dev/null +++ b/examples/ndp/ndp_user/2.list_summary.py @@ -0,0 +1,28 @@ +# examples/ndp/ndp_developer/2.list_summary.py +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate energy", "limit": 10} + ) + + if verbose: + terminal.list() + print("\n") + terminal.num_tables() + print("\n") + terminal.summary() + + terminal.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/2.process.py b/examples/ndp/ndp_user/2.process.py deleted file mode 100644 index 748e77e6..00000000 --- a/examples/ndp/ndp_user/2.process.py +++ /dev/null @@ -1,33 +0,0 @@ -# examples/ndp/ndp_developer/2.process.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts( - query=None, - kwargs={ - "keywords": "climate", - "tags": ["temperature", "humidity"], - "organization": "NASA", - "formats": ["CSV", "JSON"], - "limit": 10 - } - ) - - tables = backend.process_artifacts() - if verbose: - print("Processed artifacts preview:") - for table_name, table_data in tables.items(): - print(table_name) - for col in table_data: - print(f" {col}: {table_data[col][:3]}") # first 3 rows - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP process example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/3.display.py b/examples/ndp/ndp_user/3.display.py new file mode 100644 index 00000000..6aa2f16e --- /dev/null +++ b/examples/ndp/ndp_user/3.display.py @@ -0,0 +1,40 @@ +# examples/ndp/ndp_developer/3.disply.py +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "water quality", "limit": 3} + ) + + if verbose: + terminal.display("datasets", num_rows=5) + + terminal.display( + "datasets", + num_rows=3, + display_cols=["title", "organization", "num_resources"] + ) + + table_names = list(terminal.list(collection=True)) + resource_tables = [t for t in table_names if t != "datasets"] + + if resource_tables: + terminal.display( + resource_tables[0], + num_rows=5, + display_cols=["resource_name", "format", "url"] + ) + + terminal.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/3.query.py b/examples/ndp/ndp_user/3.query.py deleted file mode 100644 index 0ace02a9..00000000 --- a/examples/ndp/ndp_user/3.query.py +++ /dev/null @@ -1,50 +0,0 @@ -# examples/ndp/ndp_developer/3.query.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "energy", "limit": 5}) - - result = backend.query_in_memory( - "`num_resources` > 10", - {"table": "datasets", "dict_return": True} - ) - - if verbose: - print("Query results (num_resources > 10):") - for table_name, table_data in result.items(): - print(table_name, list(table_data)) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP query example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) - -# from dsi.core import DSI - -# # 1) Initialize DSI with the NDP backend -# dsi = DSI(backend_name="NDP") - -# # 2) Load a dataset into the backend (example: CSV via standard reader) -# dsi.read( -# data_sources="traffic_data.csv", -# reader_name="CSV", -# table_name="traffic" -# ) - -# # 3) Run a SQL query on the NDP-backed dataset -# result = dsi.query(""" -# SELECT road, -# AVG(speed) AS avg_speed, -# COUNT(*) AS observations -# FROM traffic -# GROUP BY road -# ORDER BY avg_speed DESC -# """, collection=True) - -# print(result) \ No newline at end of file diff --git a/examples/ndp/ndp_user/4.find.py b/examples/ndp/ndp_user/4.find.py deleted file mode 100644 index 7354fb93..00000000 --- a/examples/ndp/ndp_user/4.find.py +++ /dev/null @@ -1,25 +0,0 @@ -# examples/ndp/ndp_developer/4.find.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "climate", "limit": 5}) - - tables_found = backend.find_table("datasets") - columns_found = backend.find_column("title") - cells_found = backend.find_cell("Canada") - - if verbose: - print("Tables matching 'datasets':", [v.t_name for v in tables_found]) - print("Columns matching 'title':", [v.c_name for v in columns_found]) - print("Cells matching 'Canada':", [(v.t_name, v.c_name, v.value) for v in cells_found]) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP find example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/4.query.py b/examples/ndp/ndp_user/4.query.py new file mode 100644 index 00000000..fd1bb6fe --- /dev/null +++ b/examples/ndp/ndp_user/4.query.py @@ -0,0 +1,52 @@ +# examples/ndp/ndp_developer/4.query.py +import argparse +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + # Load NDP backend (ingest step) + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "energy", "limit": 10} + ) + + backend = terminal.active_modules["back-read"][0] + + # Query with dict_return=True returns OrderedDict + result = backend.query_artifacts( + query="`num_resources` > 5", + dict_return=True + ) + + if verbose: + print("\n=== Query Results (num_resources > 5) ===") + + for table_name, table_data in result.items(): + print(f"\n[{table_name}]") + print(f" Columns: {list(table_data.keys())}") + + # Get number of rows + num_rows = len(list(table_data.values())[0]) if table_data else 0 + print(f" Total Rows: {num_rows}") + + # Print each row with its num_resources + if 'num_resources' in table_data and num_rows > 0: + print(f"\n Rows with resource counts:") + for i in range(num_rows): + title = table_data.get('title', ['N/A'] * num_rows)[i] + resources = table_data['num_resources'][i] + org = table_data.get('organization', ['N/A'] * num_rows)[i] + + # Truncate title if too long + title_display = title[:50] + '...' if len(str(title)) > 50 else title + + print(f" Row {i+1}: {resources} resources - {title_display} ({org})") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="NDP query example") + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/5.find.py b/examples/ndp/ndp_user/5.find.py new file mode 100644 index 00000000..2b90cd10 --- /dev/null +++ b/examples/ndp/ndp_user/5.find.py @@ -0,0 +1,43 @@ +# examples/ndp/ndp_developer/5.find.py +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate ocean", "limit": 10} + ) + + backend = terminal.active_modules["back-read"][0] + + tables_found = backend.find_table("datasets") + columns_found = backend.find_column("title") + cells_found = backend.find_cell("climate") + + if verbose: + print(f"\n=== Tables matching 'datasets': {len(tables_found)} ===") + for v in tables_found: + print(f" {v.t_name}") + print(f" Columns ({len(v.c_name)}): {', '.join(v.c_name[:5])}...") + + print(f"\n=== Columns matching 'title': {len(columns_found)} ===") + for v in columns_found[:3]: + print(f" {v.t_name}.{v.c_name[0]}") + print(f" Sample values: {v.value[:2]}") + + print(f"\n=== Cells matching 'climate': {len(cells_found)} ===") + for v in cells_found[:5]: + preview = str(v.value)[:60] + "..." if len(str(v.value)) > 60 else str(v.value) + print(f" {v.t_name}.{v.c_name[0]}[{v.row_num}]: {preview}") + + terminal.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/5.inspect.py b/examples/ndp/ndp_user/5.inspect.py deleted file mode 100644 index c32659fb..00000000 --- a/examples/ndp/ndp_user/5.inspect.py +++ /dev/null @@ -1,25 +0,0 @@ -# examples/ndp/ndp_developer/5.inspect.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "climate", "limit": 5}) - - meta = backend.inspect_artifacts() - artifacts_1 = backend.get_artifacts() - artifacts_2 = backend.read_to_artifacts() - - if verbose: - print("Inspect metadata summary:", meta) - print("Artifacts (get_artifacts):", {k: list(v.keys()) for k, v in artifacts_1.items()}) - print("Artifacts (read_to_artifacts):", {k: list(v.keys()) for k, v in artifacts_2.items()}) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP inspect example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/6.process.py b/examples/ndp/ndp_user/6.process.py new file mode 100644 index 00000000..e71a295a --- /dev/null +++ b/examples/ndp/ndp_user/6.process.py @@ -0,0 +1,30 @@ +# examples/ndp/ndp_developer/6.process.py +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 10} + ) + + backend = terminal.active_modules["back-read"][0] + tables = backend.process_artifacts() + + if verbose: + print("\nProcessed tables:") + for table_name in tables.keys(): + first_col = list(tables[table_name].values())[0] + print(f" {table_name}: {len(first_col)} rows") + + terminal.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/6.validate.py b/examples/ndp/ndp_user/6.validate.py deleted file mode 100644 index 513e80f4..00000000 --- a/examples/ndp/ndp_user/6.validate.py +++ /dev/null @@ -1,35 +0,0 @@ -# examples/ndp/ndp_developer/6.validate.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "wildfire", "limit": 8}) - artifacts = backend.process_artifacts() - - if verbose: - print("Datasets loaded:", list(artifacts["datasets"].keys())) - print("Resources loaded:", list(artifacts["resources"].keys())) - - backend.validate_urls() - - resources = artifacts.get("resources", {}) - urls = resources.get("url", []) - url_valid = resources.get("url_valid", []) - - if urls: - for i, (u, v) in enumerate(zip(urls, url_valid)): - if verbose or i < 5: - print(f"{i+1}. {u} -> {'Valid' if v else 'Invalid'}") - else: - if verbose: - print("No resources found. Try increasing 'limit' or check your query.") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP URL validation example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/7.notebook.py b/examples/ndp/ndp_user/7.notebook.py deleted file mode 100644 index 170def3e..00000000 --- a/examples/ndp/ndp_user/7.notebook.py +++ /dev/null @@ -1,19 +0,0 @@ -# examples/ndp/ndp_developer/7.notebook.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "wildfire", "limit": 5}) - - if verbose: - backend.notebook() - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP notebook preview example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/7.validate.py b/examples/ndp/ndp_user/7.validate.py new file mode 100644 index 00000000..769cfe62 --- /dev/null +++ b/examples/ndp/ndp_user/7.validate.py @@ -0,0 +1,39 @@ +# examples/ndp/ndp_developer/7.validate.py +from dsi.core import Terminal + +def main(verbose=False): + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + backend = terminal.active_modules["back-read"][0] + + if verbose: + print("\nValidating URLs...") + + backend.validate_urls() + + if verbose: + artifacts = backend.process_artifacts() + for table_name in backend._resource_tables[:2]: + table = artifacts.get(table_name, {}) + urls = table.get("url", []) + valid = table.get("url_valid", []) + + print(f"\n{table_name}:") + print(f" Total URLs: {len(urls)}") + print(f" Valid: {sum(valid)}/{len(valid)}") + + terminal.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/8.close.py b/examples/ndp/ndp_user/8.close.py index d73ed7fe..e536a46e 100644 --- a/examples/ndp/ndp_user/8.close.py +++ b/examples/ndp/ndp_user/8.close.py @@ -1,21 +1,31 @@ # examples/ndp/ndp_developer/8.close.py -import argparse from dsi.core import Terminal def main(verbose=False): - t = Terminal() - t.load_module("backend", "NDP", "back-read") - backend = t.active_modules["back-read"][0] - - backend.query_artifacts(query=None, kwargs={"keywords": "climate", "limit": 5}) - + terminal = Terminal() + + terminal.load_module( + "backend", + "NDP", + "back-read", + params={"keywords": "climate", "limit": 5} + ) + + backend = terminal.active_modules["back-read"][0] + + if verbose: + print(f"\nBefore close: {len(backend._cache)} tables") + backend.close() - + if verbose: - print("Backend state after close:", backend.inspect_artifacts()) + print(f"After close: {len(backend._cache)} tables") + + terminal.close() if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP close/reset example") - parser.add_argument("--verbose", action="store_true", help="Show detailed output") + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") args = parser.parse_args() main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/9.run_all.py b/examples/ndp/ndp_user/9.run_all.py index a27852a8..43ecdc67 100644 --- a/examples/ndp/ndp_user/9.run_all.py +++ b/examples/ndp/ndp_user/9.run_all.py @@ -2,13 +2,13 @@ import subprocess test_files = [ - "1.ingest.py", - "2.process.py", - "3.query.py", - "4.find.py", - "5.inspect.py", - "6.validate.py", - "7.notebook.py", + "1.load_basic.py", + "2.list_summary.py", + "3.display.py", + "4.query.py", + "5.find.py", + "6.process.py", + "7.validate.py", "8.close.py" ] From d09bd89cfa893d2528a07a87071c938465a31baf Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 02:25:55 -0600 Subject: [PATCH 068/109] feat: added user tests --- dsi/dsi.py | 2 +- examples/ndp/ndp_user/1.load_basic.py | 17 +++----- examples/ndp/ndp_user/2.list_summary.py | 28 ------------- examples/ndp/ndp_user/2.list_tables.py | 28 +++++++++++++ examples/ndp/ndp_user/3.display.py | 40 ------------------- examples/ndp/ndp_user/3.get_table.py | 38 ++++++++++++++++++ examples/ndp/ndp_user/4.query.py | 52 ------------------------- examples/ndp/ndp_user/4.search.py | 31 +++++++++++++++ examples/ndp/ndp_user/5.filter_data.py | 37 ++++++++++++++++++ examples/ndp/ndp_user/5.find.py | 43 -------------------- examples/ndp/ndp_user/6.get_columns.py | 26 +++++++++++++ examples/ndp/ndp_user/6.process.py | 30 -------------- examples/ndp/ndp_user/7.validate.py | 39 ------------------- examples/ndp/ndp_user/8.close.py | 31 --------------- 14 files changed, 167 insertions(+), 275 deletions(-) delete mode 100644 examples/ndp/ndp_user/2.list_summary.py create mode 100644 examples/ndp/ndp_user/2.list_tables.py delete mode 100644 examples/ndp/ndp_user/3.display.py create mode 100644 examples/ndp/ndp_user/3.get_table.py delete mode 100644 examples/ndp/ndp_user/4.query.py create mode 100644 examples/ndp/ndp_user/4.search.py create mode 100644 examples/ndp/ndp_user/5.filter_data.py delete mode 100644 examples/ndp/ndp_user/5.find.py create mode 100644 examples/ndp/ndp_user/6.get_columns.py delete mode 100644 examples/ndp/ndp_user/6.process.py delete mode 100644 examples/ndp/ndp_user/7.validate.py delete mode 100644 examples/ndp/ndp_user/8.close.py diff --git a/dsi/dsi.py b/dsi/dsi.py index 38aa1dec..6f13714a 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -167,7 +167,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) self.main_backend_obj = self.t.loaded_backends[0] if backend_name.lower() == "ndp": - msg = f"Created an instance of DSI with the NDP read-only backend" + msg = "Created an instance of DSI with the NDP read-only backend" elif filename != ".temp_dsi.db": msg = f"Created an instance of DSI with the {backend_name} backend: {filename}" else: diff --git a/examples/ndp/ndp_user/1.load_basic.py b/examples/ndp/ndp_user/1.load_basic.py index 191c7e1f..dda84a0d 100644 --- a/examples/ndp/ndp_user/1.load_basic.py +++ b/examples/ndp/ndp_user/1.load_basic.py @@ -2,23 +2,18 @@ from dsi.dsi import DSI def main(verbose=False): - dsi_instance = DSI( + # Initialize NDP backend with search parameters + dsi = DSI( backend_name="NDP", - keywords="temperature", - limit=5 + keywords="temperature", # Search term + limit=5 # Maximum datasets to retrieve ) - # ADD THESE DEBUG LINES: - print(f"\nDEBUG: Backend object: {dsi_instance.main_backend_obj}") - print(f"DEBUG: Backend loaded status: {dsi_instance.main_backend_obj._loaded}") - print(f"DEBUG: Cache keys: {list(dsi_instance.main_backend_obj._cache.keys())}") - print(f"DEBUG: Params used: {dsi_instance.main_backend_obj.params}") - if verbose: print("\nAvailable tables:") - dsi_instance.list() + dsi.list() - dsi_instance.close() + dsi.close() if __name__ == "__main__": import argparse diff --git a/examples/ndp/ndp_user/2.list_summary.py b/examples/ndp/ndp_user/2.list_summary.py deleted file mode 100644 index db991099..00000000 --- a/examples/ndp/ndp_user/2.list_summary.py +++ /dev/null @@ -1,28 +0,0 @@ -# examples/ndp/ndp_developer/2.list_summary.py -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "climate energy", "limit": 10} - ) - - if verbose: - terminal.list() - print("\n") - terminal.num_tables() - print("\n") - terminal.summary() - - terminal.close() - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/2.list_tables.py b/examples/ndp/ndp_user/2.list_tables.py new file mode 100644 index 00000000..2b37d7c9 --- /dev/null +++ b/examples/ndp/ndp_user/2.list_tables.py @@ -0,0 +1,28 @@ +# examples/ndp/ndp_user/2.list_tables.py +from dsi.dsi import DSI + +def main(verbose=False): + dsi = DSI( + backend_name="NDP", + keywords="climate", + limit=10 + ) + + # Print table names and dimensions + dsi.list() + + if verbose: + # Get table names as a list + table_names = dsi.list(collection=True) + print(f"\nFound {len(table_names)} tables:") + for name in table_names: + print(f" - {name}") + + dsi.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/3.display.py b/examples/ndp/ndp_user/3.display.py deleted file mode 100644 index 6aa2f16e..00000000 --- a/examples/ndp/ndp_user/3.display.py +++ /dev/null @@ -1,40 +0,0 @@ -# examples/ndp/ndp_developer/3.disply.py -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "water quality", "limit": 3} - ) - - if verbose: - terminal.display("datasets", num_rows=5) - - terminal.display( - "datasets", - num_rows=3, - display_cols=["title", "organization", "num_resources"] - ) - - table_names = list(terminal.list(collection=True)) - resource_tables = [t for t in table_names if t != "datasets"] - - if resource_tables: - terminal.display( - resource_tables[0], - num_rows=5, - display_cols=["resource_name", "format", "url"] - ) - - terminal.close() - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/3.get_table.py b/examples/ndp/ndp_user/3.get_table.py new file mode 100644 index 00000000..0bf366f6 --- /dev/null +++ b/examples/ndp/ndp_user/3.get_table.py @@ -0,0 +1,38 @@ +# examples/ndp/ndp_user/3.get_table.py +from dsi.dsi import DSI + +def main(verbose=False): + dsi = DSI( + backend_name="NDP", + keywords="ocean", + limit=15 + ) + + # Get datasets table as DataFrame + print("\nGetting datasets table:") + df = dsi.get_table("datasets", collection=True) + print(f"Shape: {df.shape}") + print(f"Columns: {list(df.columns)}") + + if verbose: + print("\nFirst 3 rows:") + print(df.head(3)) + + # Get a resource table if available + table_list = dsi.list(collection=True) + resource_tables = [t for t in table_list if t != 'datasets'] + + if resource_tables: + print(f"\nGetting resource table: {resource_tables[0]}") + resource_df = dsi.get_table(resource_tables[0], collection=True) + print(f"Shape: {resource_df.shape}") + print(resource_df.head(2)) + + dsi.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/4.query.py b/examples/ndp/ndp_user/4.query.py deleted file mode 100644 index fd1bb6fe..00000000 --- a/examples/ndp/ndp_user/4.query.py +++ /dev/null @@ -1,52 +0,0 @@ -# examples/ndp/ndp_developer/4.query.py -import argparse -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - # Load NDP backend (ingest step) - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "energy", "limit": 10} - ) - - backend = terminal.active_modules["back-read"][0] - - # Query with dict_return=True returns OrderedDict - result = backend.query_artifacts( - query="`num_resources` > 5", - dict_return=True - ) - - if verbose: - print("\n=== Query Results (num_resources > 5) ===") - - for table_name, table_data in result.items(): - print(f"\n[{table_name}]") - print(f" Columns: {list(table_data.keys())}") - - # Get number of rows - num_rows = len(list(table_data.values())[0]) if table_data else 0 - print(f" Total Rows: {num_rows}") - - # Print each row with its num_resources - if 'num_resources' in table_data and num_rows > 0: - print(f"\n Rows with resource counts:") - for i in range(num_rows): - title = table_data.get('title', ['N/A'] * num_rows)[i] - resources = table_data['num_resources'][i] - org = table_data.get('organization', ['N/A'] * num_rows)[i] - - # Truncate title if too long - title_display = title[:50] + '...' if len(str(title)) > 50 else title - - print(f" Row {i+1}: {resources} resources - {title_display} ({org})") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="NDP query example") - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/4.search.py b/examples/ndp/ndp_user/4.search.py new file mode 100644 index 00000000..5b2a2eda --- /dev/null +++ b/examples/ndp/ndp_user/4.search.py @@ -0,0 +1,31 @@ +# examples/ndp/ndp_user/4.search.py +from dsi.dsi import DSI + +def main(verbose=False): + dsi = DSI( + backend_name="NDP", + keywords="water quality", + limit=10 + ) + + # Search for CSV format across all tables + print("\nSearching for 'CSV':") + dsi.search("CSV") + + if verbose: + # Get search results as list of DataFrames + results = dsi.search("water", collection=True) + if results: + print(f"\nFound matches in {len(results)} location(s)") + for i, df in enumerate(results): + print(f"\nResult {i+1}:") + print(df.head()) + + dsi.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/5.filter_data.py b/examples/ndp/ndp_user/5.filter_data.py new file mode 100644 index 00000000..319840ae --- /dev/null +++ b/examples/ndp/ndp_user/5.filter_data.py @@ -0,0 +1,37 @@ +# examples/ndp/ndp_user/5.filter_data.py +from dsi.dsi import DSI + +def main(verbose=False): + dsi = DSI( + backend_name="NDP", + keywords="energy", + limit=20 + ) + + # Get datasets and filter + df = dsi.get_table("datasets", collection=True) + + print("\nFiltering datasets with more than 5 resources:") + filtered = df.query("num_resources > 5") + print(f"Found {len(filtered)} datasets") + print(filtered[['title', 'num_resources']]) + + if verbose: + print("\n\nFiltering by organization:") + if 'organization' in df.columns: + orgs = df['organization'].value_counts() + print(orgs.head()) + + # Filter by top organization + top_org = orgs.index[0] + org_datasets = df[df['organization'] == top_org] + print(f"\nDatasets from {top_org}: {len(org_datasets)}") + + dsi.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/5.find.py b/examples/ndp/ndp_user/5.find.py deleted file mode 100644 index 2b90cd10..00000000 --- a/examples/ndp/ndp_user/5.find.py +++ /dev/null @@ -1,43 +0,0 @@ -# examples/ndp/ndp_developer/5.find.py -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "climate ocean", "limit": 10} - ) - - backend = terminal.active_modules["back-read"][0] - - tables_found = backend.find_table("datasets") - columns_found = backend.find_column("title") - cells_found = backend.find_cell("climate") - - if verbose: - print(f"\n=== Tables matching 'datasets': {len(tables_found)} ===") - for v in tables_found: - print(f" {v.t_name}") - print(f" Columns ({len(v.c_name)}): {', '.join(v.c_name[:5])}...") - - print(f"\n=== Columns matching 'title': {len(columns_found)} ===") - for v in columns_found[:3]: - print(f" {v.t_name}.{v.c_name[0]}") - print(f" Sample values: {v.value[:2]}") - - print(f"\n=== Cells matching 'climate': {len(cells_found)} ===") - for v in cells_found[:5]: - preview = str(v.value)[:60] + "..." if len(str(v.value)) > 60 else str(v.value) - print(f" {v.t_name}.{v.c_name[0]}[{v.row_num}]: {preview}") - - terminal.close() - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/6.get_columns.py b/examples/ndp/ndp_user/6.get_columns.py new file mode 100644 index 00000000..62162ec2 --- /dev/null +++ b/examples/ndp/ndp_user/6.get_columns.py @@ -0,0 +1,26 @@ +# examples/ndp/ndp_developer/6.get_columns.py +from dsi.dsi import DSI + +def main(verbose=False): + dsi = DSI(backend_name="NDP", keywords="climate", limit=10) + + df = dsi.get_table("datasets", collection=True) + + # Select columns + subset = df[['title', 'organization', 'num_resources']] + print("\nDataset summary:") + print(subset) + + if verbose: + print("\n\nAvailable columns:") + for col in df.columns: + print(f" - {col} ({df[col].dtype})") + + dsi.close() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/6.process.py b/examples/ndp/ndp_user/6.process.py deleted file mode 100644 index e71a295a..00000000 --- a/examples/ndp/ndp_user/6.process.py +++ /dev/null @@ -1,30 +0,0 @@ -# examples/ndp/ndp_developer/6.process.py -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "climate", "limit": 10} - ) - - backend = terminal.active_modules["back-read"][0] - tables = backend.process_artifacts() - - if verbose: - print("\nProcessed tables:") - for table_name in tables.keys(): - first_col = list(tables[table_name].values())[0] - print(f" {table_name}: {len(first_col)} rows") - - terminal.close() - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/7.validate.py b/examples/ndp/ndp_user/7.validate.py deleted file mode 100644 index 769cfe62..00000000 --- a/examples/ndp/ndp_user/7.validate.py +++ /dev/null @@ -1,39 +0,0 @@ -# examples/ndp/ndp_developer/7.validate.py -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "climate", "limit": 5} - ) - - backend = terminal.active_modules["back-read"][0] - - if verbose: - print("\nValidating URLs...") - - backend.validate_urls() - - if verbose: - artifacts = backend.process_artifacts() - for table_name in backend._resource_tables[:2]: - table = artifacts.get(table_name, {}) - urls = table.get("url", []) - valid = table.get("url_valid", []) - - print(f"\n{table_name}:") - print(f" Total URLs: {len(urls)}") - print(f" Valid: {sum(valid)}/{len(valid)}") - - terminal.close() - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file diff --git a/examples/ndp/ndp_user/8.close.py b/examples/ndp/ndp_user/8.close.py deleted file mode 100644 index e536a46e..00000000 --- a/examples/ndp/ndp_user/8.close.py +++ /dev/null @@ -1,31 +0,0 @@ -# examples/ndp/ndp_developer/8.close.py -from dsi.core import Terminal - -def main(verbose=False): - terminal = Terminal() - - terminal.load_module( - "backend", - "NDP", - "back-read", - params={"keywords": "climate", "limit": 5} - ) - - backend = terminal.active_modules["back-read"][0] - - if verbose: - print(f"\nBefore close: {len(backend._cache)} tables") - - backend.close() - - if verbose: - print(f"After close: {len(backend._cache)} tables") - - terminal.close() - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - main(verbose=args.verbose) \ No newline at end of file From 9c043c09e6ac956385c61ea5bfaea879d7101016 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 02:28:33 -0600 Subject: [PATCH 069/109] fix: added dsi test --- dsi/tests/test_dsi.py | 220 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 671d0397..42e899c0 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1797,6 +1797,226 @@ def test_fail_overwrite_schema_duckdb_backend(): except Exception as e: expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." assert str(e) == expected + + +# NDP +# NDP +# NDP + +def test_ndp_backend(): + """Test basic NDP connection""" + dsi = DSI(backend_name="NDP", keywords="test", limit=3) + dsi.close() + assert True + +def test_list_ndp_backend(): + """Test listing NDP tables""" + dsi = DSI(backend_name="NDP", keywords="climate", limit=5) + + # Should have datasets table and possibly resource tables + tables = dsi.list(collection=True) + assert isinstance(tables, list) + assert len(tables) > 0 + assert "datasets" in tables + + dsi.close() + +def test_get_table_ndp_backend(): + """Test getting tables from NDP""" + dsi = DSI(backend_name="NDP", keywords="ocean", limit=10) + + # Test getting datasets table + f = io.StringIO() + with redirect_stdout(f): + dsi.get_table(table_name="datasets") + output = f.getvalue() + + # Verify output contains table data + assert len(output) > 0 + + # Test with collection=True + df = dsi.get_table(table_name="datasets", collection=True) + assert isinstance(df, DataFrame) + assert len(df) > 0 + assert 'title' in df.columns + assert 'num_resources' in df.columns + + dsi.close() + +def test_search_ndp_backend(): + """Test searching in NDP backend""" + dsi = DSI(backend_name="NDP", keywords="data", limit=5) + + # Test search output + f = io.StringIO() + with redirect_stdout(f): + dsi.search(query="CSV") + output = f.getvalue() + + # Verify search message appears + assert "Searching for all instances of CSV in the active backend" in output + + # Test search with collection=True + results = dsi.search(query="CSV", collection=True) + assert isinstance(results, list) + # Results may be empty, just verify it returns a list + + dsi.close() + +def test_find_ndp_backend(): + """Test finding datasets in NDP backend""" + dsi = DSI(backend_name="NDP", keywords="climate", limit=10) + + # Get datasets table for filtering + df = dsi.get_table(table_name="datasets", collection=True) + assert 'num_resources' in df.columns + + # Test filtering datasets with resources + filtered = df[df['num_resources'] > 0] + assert len(filtered) >= 0 # May or may not have resources + + # Test filtering by title + if 'title' in df.columns and len(df) > 0: + # Just verify we can access and filter the column + assert df['title'].dtype == object # String column + + dsi.close() + +def test_query_ndp_backend(): + """Test that NDP doesn't support direct SQL queries""" + dsi = DSI(backend_name="NDP", keywords="test", limit=3) + + # NDP backend doesn't support SQL queries + # This test verifies the backend works without query() method + tables = dsi.list(collection=True) + assert "datasets" in tables + + dsi.close() + +def test_resources_ndp_backend(): + """Test accessing resource tables in NDP backend""" + dsi = DSI(backend_name="NDP", keywords="climate", limit=5) + + # Get all tables + tables = dsi.list(collection=True) + + # Find resource tables (not 'datasets') + resource_tables = [t for t in tables if t != 'datasets'] + + # If there are resource tables, verify we can access them + if len(resource_tables) > 0: + first_resource_table = resource_tables[0] + + # Test output + f = io.StringIO() + with redirect_stdout(f): + dsi.get_table(table_name=first_resource_table) + output = f.getvalue() + assert len(output) > 0 + + # Test collection + resource_df = dsi.get_table(table_name=first_resource_table, collection=True) + assert isinstance(resource_df, DataFrame) + assert len(resource_df) > 0 + + dsi.close() + +def test_multiple_connections_ndp_backend(): + """Test multiple connection cycles for NDP backend""" + # Test multiple open/close cycles + for i in range(3): + dsi = DSI(backend_name="NDP", keywords="test", limit=2) + df = dsi.get_table("datasets", collection=True) + assert df is not None + assert len(df) > 0 + dsi.close() + + assert True + +def test_different_keywords_ndp_backend(): + """Test NDP with different keyword searches""" + keywords_list = ["ocean", "climate", "water"] + + for keyword in keywords_list: + dsi = DSI(backend_name="NDP", keywords=keyword, limit=5) + + # Verify we can get datasets table + df = dsi.get_table("datasets", collection=True) + assert isinstance(df, DataFrame) + assert len(df) > 0 + + dsi.close() + + assert True + +def test_limit_parameter_ndp_backend(): + """Test NDP with different limit values""" + # Test with limit=3 + dsi1 = DSI(backend_name="NDP", keywords="data", limit=3) + df1 = dsi1.get_table("datasets", collection=True) + dsi1.close() + + # Test with limit=10 + dsi2 = DSI(backend_name="NDP", keywords="data", limit=10) + df2 = dsi2.get_table("datasets", collection=True) + dsi2.close() + + # Verify both return dataframes + assert isinstance(df1, DataFrame) + assert isinstance(df2, DataFrame) + + # Note: Can't guarantee df2 > df1 due to API behavior + assert len(df1) > 0 + assert len(df2) > 0 + + assert True + +def test_datasets_structure_ndp_backend(): + """Test that datasets table has expected structure""" + dsi = DSI(backend_name="NDP", keywords="climate", limit=5) + + df = dsi.get_table("datasets", collection=True) + + # Verify it's a DataFrame + assert isinstance(df, DataFrame) + assert len(df) > 0 + + # Verify key columns exist + expected_columns = ['title', 'num_resources'] + for col in expected_columns: + assert col in df.columns, f"Missing expected column: {col}" + + # Verify data types + assert df['num_resources'].dtype in ['int64', 'int32', 'float64'] + assert df['title'].dtype == object # String column + + dsi.close() + +def test_error_invalid_backend_ndp(): + """Test error handling for invalid NDP parameters""" + # Test with empty keywords + try: + dsi = DSI(backend_name="NDP", keywords="", limit=5) + dsi.close() + # Empty keywords might work, just verify connection + assert True + except: + # Or it might error - either is acceptable + assert True + +def test_close_ndp_backend(): + """Test proper closing of NDP connections""" + dsi = DSI(backend_name="NDP", keywords="test", limit=3) + + # Verify connection is active + tables = dsi.list(collection=True) + assert len(tables) > 0 + + # Close connection + dsi.close() + + # Test is successful if close() doesn't raise exception + assert True # def main(): def test_versioning(): From af54ca557011d628530ecefe30bdce8bf2aa23e5 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 02:30:25 -0600 Subject: [PATCH 070/109] fix: dsi test --- dsi/tests/test_dsi.py | 77 ++++--------------------------------------- 1 file changed, 6 insertions(+), 71 deletions(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 42e899c0..cbc4954a 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1813,7 +1813,6 @@ def test_list_ndp_backend(): """Test listing NDP tables""" dsi = DSI(backend_name="NDP", keywords="climate", limit=5) - # Should have datasets table and possibly resource tables tables = dsi.list(collection=True) assert isinstance(tables, list) assert len(tables) > 0 @@ -1825,16 +1824,14 @@ def test_get_table_ndp_backend(): """Test getting tables from NDP""" dsi = DSI(backend_name="NDP", keywords="ocean", limit=10) - # Test getting datasets table + # Test display output f = io.StringIO() with redirect_stdout(f): dsi.get_table(table_name="datasets") output = f.getvalue() - - # Verify output contains table data assert len(output) > 0 - # Test with collection=True + # Test collection df = dsi.get_table(table_name="datasets", collection=True) assert isinstance(df, DataFrame) assert len(df) > 0 @@ -1847,88 +1844,26 @@ def test_search_ndp_backend(): """Test searching in NDP backend""" dsi = DSI(backend_name="NDP", keywords="data", limit=5) - # Test search output + # Test display output f = io.StringIO() with redirect_stdout(f): dsi.search(query="CSV") output = f.getvalue() - - # Verify search message appears assert "Searching for all instances of CSV in the active backend" in output - # Test search with collection=True + # Test collection results = dsi.search(query="CSV", collection=True) assert isinstance(results, list) - # Results may be empty, just verify it returns a list dsi.close() -def test_find_ndp_backend(): - """Test finding datasets in NDP backend""" - dsi = DSI(backend_name="NDP", keywords="climate", limit=10) - - # Get datasets table for filtering - df = dsi.get_table(table_name="datasets", collection=True) - assert 'num_resources' in df.columns - - # Test filtering datasets with resources - filtered = df[df['num_resources'] > 0] - assert len(filtered) >= 0 # May or may not have resources - - # Test filtering by title - if 'title' in df.columns and len(df) > 0: - # Just verify we can access and filter the column - assert df['title'].dtype == object # String column - - dsi.close() - -def test_query_ndp_backend(): - """Test that NDP doesn't support direct SQL queries""" - dsi = DSI(backend_name="NDP", keywords="test", limit=3) - - # NDP backend doesn't support SQL queries - # This test verifies the backend works without query() method - tables = dsi.list(collection=True) - assert "datasets" in tables - - dsi.close() - -def test_resources_ndp_backend(): - """Test accessing resource tables in NDP backend""" - dsi = DSI(backend_name="NDP", keywords="climate", limit=5) - - # Get all tables - tables = dsi.list(collection=True) - - # Find resource tables (not 'datasets') - resource_tables = [t for t in tables if t != 'datasets'] - - # If there are resource tables, verify we can access them - if len(resource_tables) > 0: - first_resource_table = resource_tables[0] - - # Test output - f = io.StringIO() - with redirect_stdout(f): - dsi.get_table(table_name=first_resource_table) - output = f.getvalue() - assert len(output) > 0 - - # Test collection - resource_df = dsi.get_table(table_name=first_resource_table, collection=True) - assert isinstance(resource_df, DataFrame) - assert len(resource_df) > 0 - - dsi.close() - -def test_multiple_connections_ndp_backend(): - """Test multiple connection cycles for NDP backend""" +def test_close_ndp_backend(): + """Test connection management""" # Test multiple open/close cycles for i in range(3): dsi = DSI(backend_name="NDP", keywords="test", limit=2) df = dsi.get_table("datasets", collection=True) assert df is not None - assert len(df) > 0 dsi.close() assert True From 74a18949922983f3b4ac4a896287b4b1135529bc Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 02:32:15 -0600 Subject: [PATCH 071/109] fix: dsi test errors --- dsi/tests/test_dsi.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index cbc4954a..6cf781bc 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1814,9 +1814,10 @@ def test_list_ndp_backend(): dsi = DSI(backend_name="NDP", keywords="climate", limit=5) tables = dsi.list(collection=True) - assert isinstance(tables, list) - assert len(tables) > 0 - assert "datasets" in tables + tables_list = list(tables) # Convert odict_keys to list + assert isinstance(tables_list, list) + assert len(tables_list) > 0 + assert "datasets" in tables_list dsi.close() From 00f52958ad6e202c3f04339de45d13a45ff3d3d7 Mon Sep 17 00:00:00 2001 From: krhom Date: Fri, 17 Apr 2026 02:34:25 -0600 Subject: [PATCH 072/109] fix: dsi test --- dsi/tests/test_dsi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 6cf781bc..1ab996a9 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -1850,7 +1850,7 @@ def test_search_ndp_backend(): with redirect_stdout(f): dsi.search(query="CSV") output = f.getvalue() - assert "Searching for all instances of CSV in the active backend" in output + assert "Searching for all instances of 'CSV' in the active backend" in output # Test collection results = dsi.search(query="CSV", collection=True) From c6ffbe7cb14dc01f93ae8255037d02789f60826d Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Fri, 17 Apr 2026 13:50:04 -0600 Subject: [PATCH 073/109] staging next version --- dsi/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/_version.py b/dsi/_version.py index 38301122..29047e91 100644 --- a/dsi/_version.py +++ b/dsi/_version.py @@ -1 +1 @@ -__version__ = "1.1.5" \ No newline at end of file +__version__ = "1.2" \ No newline at end of file From 12a4b4e39675c467c862c5a4ef52c77dc6aa9bc1 Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Fri, 17 Apr 2026 13:52:34 -0600 Subject: [PATCH 074/109] cleaned up test ndp pip --- .github/workflows/test_ndp.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test_ndp.yml b/.github/workflows/test_ndp.yml index e107e919..463e2a50 100644 --- a/.github/workflows/test_ndp.yml +++ b/.github/workflows/test_ndp.yml @@ -26,9 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt pip install -r requirements.extras.txt - pip install . - name: Test NDP Backend run: | From 5ca139ab42bfae9ec36e861e2598bf73a4843abf Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Fri, 17 Apr 2026 14:01:31 -0600 Subject: [PATCH 075/109] dot fix --- .github/workflows/test_ndp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_ndp.yml b/.github/workflows/test_ndp.yml index 463e2a50..0f63db0f 100644 --- a/.github/workflows/test_ndp.yml +++ b/.github/workflows/test_ndp.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.extras.txt + pip install . -r requirements.extras.txt - name: Test NDP Backend run: | From b530eb2ee5bbfb7c0f14681d082701b942e3885e Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Fri, 17 Apr 2026 14:14:24 -0600 Subject: [PATCH 076/109] ndp future implement --- dsi/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dsi/cli.py b/dsi/cli.py index 2fa0ab0e..6d3cafac 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -108,6 +108,7 @@ def startup(self, backend="sqlite"): self.t.load_module('backend','DuckDB','back-write', filename = self.db_path) self.name = "duckdb" elif backend.lower() == "ndp": + # PLACEHOLDER for future implement # NDP requires params dict for initialization print("NDP backend requires configuration.") print("Use Python API: t.load_module('backend', 'NDP', 'back-read', params={...})") From 649d3929ad83955a868cb876bb52dee8c6ca0c6c Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Fri, 17 Apr 2026 14:55:17 -0600 Subject: [PATCH 077/109] added checks to list valid backends --- dsi/backends/ndp.py | 61 ++++++++++++++++++++++++--------------------- dsi/dsi.py | 9 +++++-- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index e49d4f54..3bb53574 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -158,7 +158,7 @@ def validate_connection(self): test_url, headers=self.headers, verify=self.verify_ssl, - timeout=10 + timeout=2 ) response.raise_for_status() @@ -170,34 +170,37 @@ def validate_connection(self): ) return True - - except requests.exceptions.Timeout: - raise ConnectionError( - f"Connection timeout: Unable to reach {self.base_url} within 10 seconds" - ) - except requests.exceptions.ConnectionError: - raise ConnectionError( - f"Connection failed: Unable to connect to {self.base_url}. " - "Check the URL and your network connection." - ) - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - raise RuntimeError( - f"CKAN API not found at {self.base_url}. " - "Verify this is a valid CKAN endpoint." - ) - else: - raise RuntimeError( - f"HTTP {e.response.status_code} Error: {str(e)}" - ) - except requests.exceptions.RequestException as e: - raise ConnectionError( - f"Failed to validate connection to {self.base_url}: {str(e)}" - ) - except ValueError as e: - raise RuntimeError( - f"Invalid JSON response from {self.base_url}: {str(e)}" - ) + except: + # Need to silent exit to continue external workflows + return False + + # except requests.exceptions.Timeout: + # raise ConnectionError( + # f"Connection timeout: Unable to reach {self.base_url} within 10 seconds" + # ) + # except requests.exceptions.ConnectionError: + # raise ConnectionError( + # f"Connection failed: Unable to connect to {self.base_url}. " + # "Check the URL and your network connection." + # ) + # except requests.exceptions.HTTPError as e: + # if e.response.status_code == 404: + # raise RuntimeError( + # f"CKAN API not found at {self.base_url}. " + # "Verify this is a valid CKAN endpoint." + # ) + # else: + # raise RuntimeError( + # f"HTTP {e.response.status_code} Error: {str(e)}" + # ) + # except requests.exceptions.RequestException as e: + # raise ConnectionError( + # f"Failed to validate connection to {self.base_url}: {str(e)}" + # ) + # except ValueError as e: + # raise RuntimeError( + # f"Invalid JSON response from {self.base_url}: {str(e)}" + # ) # ---------------------------------------------------------------------- diff --git a/dsi/dsi.py b/dsi/dsi.py index 6f13714a..db74cc89 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -1,9 +1,11 @@ from dsi.core import Terminal #, Sync +from dsi.backends.ndp import NDP from collections import OrderedDict import numpy as np import pandas as pd import os import logging +import importlib.util from contextlib import redirect_stdout import io import math @@ -181,8 +183,11 @@ def list_backends(self): """ print("\nValid Backends for `backend_name` in backend():\n" + "-" * 40) print("Sqlite : Lightweight, file-based SQL backend. Default backend used by DSI API.") - print("DuckDB : In-process SQL backend optimized for fast analytics on large datasets.\n") - print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.\n") + if importlib.util.find_spec("duckdb") is not None: + print("DuckDB : In-process SQL backend optimized for fast analytics on large datasets.") + n = NDP() + if n.validate_connection(): + print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.\n") print() def schema(self, filename = None): From e611e03b2374e1a69e5697e20159eabb3a54ecb6 Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Fri, 17 Apr 2026 15:00:32 -0600 Subject: [PATCH 078/109] bypass linter --- dsi/backends/ndp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/backends/ndp.py b/dsi/backends/ndp.py index 3bb53574..aa0cc7d1 100644 --- a/dsi/backends/ndp.py +++ b/dsi/backends/ndp.py @@ -170,7 +170,7 @@ def validate_connection(self): ) return True - except: + except: # noqa: E722 # Need to silent exit to continue external workflows return False From c43b8f3dba30c1b81e58c33b797a87cc0a07009b Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Tue, 21 Apr 2026 14:12:26 -0600 Subject: [PATCH 079/109] fixed api access location --- examples/dsi_tutorial.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dsi_tutorial.ipynb b/examples/dsi_tutorial.ipynb index 6be736eb..3ba43552 100644 --- a/examples/dsi_tutorial.ipynb +++ b/examples/dsi_tutorial.ipynb @@ -676,7 +676,7 @@ "metadata": {}, "outputs": [], "source": [ - "from dsi.core import Sync" + "from dsi.sync import Sync" ] }, { From fa12c4a3402e371bbdc32d333bb2aba99ec380ca Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Tue, 28 Apr 2026 17:47:00 -0600 Subject: [PATCH 080/109] staging intermediate changes --- examples/dsi_tutorial.ipynb | 55 +++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/examples/dsi_tutorial.ipynb b/examples/dsi_tutorial.ipynb index 3ba43552..097bc5a5 100644 --- a/examples/dsi_tutorial.ipynb +++ b/examples/dsi_tutorial.ipynb @@ -669,6 +669,15 @@ "# Moving your data with DSI" ] }, + { + "cell_type": "markdown", + "id": "ca63f02d", + "metadata": {}, + "source": [ + "Once metadata has been ingested, you can use DSI's copy/move capabilities to stage and target local or remote locations.\n", + "On a DSI move, first a local data location and remote location need to be defined. DSI requires an indexing step to for internal book-keeping. DSI will not copy or move until an index is ran." + ] + }, { "cell_type": "code", "execution_count": null, @@ -679,6 +688,14 @@ "from dsi.sync import Sync" ] }, + { + "cell_type": "markdown", + "id": "e014f861", + "metadata": {}, + "source": [ + "Both file paths should be absolute paths, ../../'s are not allowed for example" + ] + }, { "cell_type": "code", "execution_count": null, @@ -692,6 +709,16 @@ "remote_path = \"/Users/Shared/staging/\"" ] }, + { + "cell_type": "markdown", + "id": "c2ef2c1b", + "metadata": {}, + "source": [ + "Create a Sync object. DSI expects the 'project name' to be the name of your database file. \n", + "Rename the database file before this step if you'd like to rename the project, before indexing.\n", + "For example, 'dsi-tutorial.db' will be the project name for this example." + ] + }, { "cell_type": "code", "execution_count": null, @@ -703,6 +730,14 @@ "s = Sync(\"dsi-tutorial\")" ] }, + { + "cell_type": "markdown", + "id": "b55f1fc8", + "metadata": {}, + "source": [ + "Run the index call, passing the True flag will enable debug mode that prints the progress of the index (filecrawl) and the entry into the database." + ] + }, { "cell_type": "code", "execution_count": null, @@ -713,6 +748,14 @@ "s.index(local_files,remote_path,True)" ] }, + { + "cell_type": "markdown", + "id": "5be1d996", + "metadata": {}, + "source": [ + "(Optional) Print out the status of the database. This needs to use the previous store object (not Sync object)." + ] + }, { "cell_type": "code", "execution_count": null, @@ -723,6 +766,14 @@ "store.summary()" ] }, + { + "cell_type": "markdown", + "id": "a511258e", + "metadata": {}, + "source": [ + "The user needs to invoke the DSI Copy method. By calling \"copy\", this will initiate a local filesystem Unix/Windows copy to move the data and metadata database to the remote location. DSI will automatically create a new folder at the remote location that is the name of the project, and then attempt to initiate a copy. True will print out the status of the move." + ] + }, { "cell_type": "code", "execution_count": null, @@ -736,7 +787,7 @@ ], "metadata": { "kernelspec": { - "display_name": "mydsi", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -750,7 +801,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.10.11" } }, "nbformat": 4, From 4d09ab1c9922d76ed3f2c4231eff6698581bfd7a Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Tue, 28 Apr 2026 18:27:20 -0600 Subject: [PATCH 081/109] minor update to raising error in core.ingest artifacts --- dsi/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/core.py b/dsi/core.py index 3a55f5d7..191e34d6 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -481,7 +481,7 @@ def artifact_handler(self, interaction_type, query = None, **kwargs): e.args = (f"Error ingesting data - {str(e.args[0])}", *e.args[1:]) else: e.args = (f"Error ingesting data in {original_file} @ line {return_line_number} - {str(e.args[0])}", *e.args[1:]) - raise + raise e from None if tester == 1: sys.settrace(None) # ends trace to prevent large overhead operation_success = True From 582a415658efda9ece53f300a8119c3a763fdefd Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Tue, 28 Apr 2026 18:31:33 -0600 Subject: [PATCH 082/109] updated raise errors in core to use 'from None' for exceptions --- dsi/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsi/core.py b/dsi/core.py index 191e34d6..22e0848f 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -199,7 +199,7 @@ def load_module(self, mod_type, mod_name, mod_function, **kwargs): e.args = (f'Error in {original_file} @ line {return_line_number}: {str(e.args[0])}', *e.args[1:]) else: e.args = (f'Error in {original_file} @ line {return_line_number}',) - raise + raise e from None if tester == 1: sys.settrace(None) # ends trace to prevent large overhead @@ -396,7 +396,7 @@ def transload(self, **kwargs): e.args = (f'Error in {original_file} @ line {return_line_number}: {str(e.args[0])}', *e.args[1:]) else: e.args = (f'Error in {original_file} @ line {return_line_number}',) - raise + raise e from None if tester == 1: sys.settrace(None) # ends trace to prevent large overhead @@ -514,7 +514,7 @@ def artifact_handler(self, interaction_type, query = None, **kwargs): self.logger.error((str(e))) if not self.user_wrapper: e.args = (f"Caught error in {original_file} @ line {return_line_number}: " + e.args[0], *e.args[1:]) - raise + raise e from None if tester == 1: sys.settrace(None) # ends trace to prevent large overhead operation_success = True From 3a924b43c04396f59438271158934fb0807be884 Mon Sep 17 00:00:00 2001 From: krhom Date: Wed, 22 Apr 2026 11:17:16 -0600 Subject: [PATCH 083/109] feat: added ndp cli changes --- dsi/cli.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dsi/cli.py b/dsi/cli.py index 6d3cafac..c52da2b1 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -108,11 +108,10 @@ def startup(self, backend="sqlite"): self.t.load_module('backend','DuckDB','back-write', filename = self.db_path) self.name = "duckdb" elif backend.lower() == "ndp": - # PLACEHOLDER for future implement # NDP requires params dict for initialization - print("NDP backend requires configuration.") - print("Use Python API: t.load_module('backend', 'NDP', 'back-read', params={...})") - self.exit_cli([]) + keywords = input("Please input the ndp keywords: ").strip() + self.t.load_module('backend', 'NDP', 'back-read', params={"keywords": keywords}) + self.name = "ndp" else: backend = "sqlite" self.t.load_module('backend','Sqlite','back-write', filename = self.db_path) From 9e4e3835e73a2da191aeec6adb6979e19fec5adf Mon Sep 17 00:00:00 2001 From: krhom Date: Wed, 22 Apr 2026 11:23:51 -0600 Subject: [PATCH 084/109] fix: printing issue, tested with summary, list, display, and search --- dsi/cli.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dsi/cli.py b/dsi/cli.py index c52da2b1..097b65c8 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -101,7 +101,11 @@ def startup(self, backend="sqlite"): if os.path.exists(self.db_path): os.remove(self.db_path) - + + keywords = None + if backend.lower() == "ndp": + keywords = input("Enter NDP search keywords (e.g., climate, temperature): ").strip() + try: with redirect_stdout(fnull): if backend=="duckdb": @@ -109,7 +113,6 @@ def startup(self, backend="sqlite"): self.name = "duckdb" elif backend.lower() == "ndp": # NDP requires params dict for initialization - keywords = input("Please input the ndp keywords: ").strip() self.t.load_module('backend', 'NDP', 'back-read', params={"keywords": keywords}) self.name = "ndp" else: @@ -880,8 +883,8 @@ def main(): parser.add_argument("-b", "--backend", type=str, default="sqlite", help="Supported backends are sqlite and duckdb") args = parser.parse_args() - if args.backend.lower() not in ["sqlite", "duckdb"]: - print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb") + if args.backend.lower() not in ["sqlite", "duckdb", "ndp"]: + print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, ndp") exit(1) print(" ", textwrap.dedent(fr""" _____ ___ From c8f8864025ab8370eb560dc4c2e4c802e63d3f62 Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Wed, 22 Apr 2026 12:31:18 -0600 Subject: [PATCH 085/109] added ndp to cli -b flag help --- dsi/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsi/cli.py b/dsi/cli.py index 097b65c8..105b0258 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -880,11 +880,11 @@ def main(): exit(0) parser = argparse.ArgumentParser() - parser.add_argument("-b", "--backend", type=str, default="sqlite", help="Supported backends are sqlite and duckdb") + parser.add_argument("-b", "--backend", type=str, default="sqlite", help="Supported backends are sqlite, duckdb, and ndp") args = parser.parse_args() if args.backend.lower() not in ["sqlite", "duckdb", "ndp"]: - print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, ndp") + print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, and ndp") exit(1) print(" ", textwrap.dedent(fr""" _____ ___ From 7913fad102a5dc1bbf3421d3ddef43e809bb7e2c Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Fri, 24 Apr 2026 12:36:45 -0600 Subject: [PATCH 086/109] added process() to dsi.py that converts data from one format to another --- dsi/dsi.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/dsi/dsi.py b/dsi/dsi.py index db74cc89..946cbe46 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -836,6 +836,71 @@ def update(self, collection, backup = False): e.args = (f'update() ERROR: {str(e.args[0])}',) + e.args[1:] raise + def process(self, backend_name, filename, **kwargs): + """ + Process is processing collections into a specific format: Sqlite or DuckDB for now + + DSI instance will now use that new backend as the base for all functions. + """ + if not self.t.valid_backend(self.main_backend_obj, self.main_backend_obj.__class__.__bases__[0].__name__): + raise RuntimeError("ERROR: Cannot process() data from an empty backend. Please ensure there is data in it.") + if self.schema_read: + raise RuntimeError("ERROR: Cannot process() until all associated data is loaded after a complex schema") + + try: + self.t.artifact_handler(interaction_type='process') + except Exception as e: + if e.args: + e.args = (f'process() ERROR: {str(e.args[0])}',) + e.args[1:] + raise + + old_backend_data = self.t.active_metadata + + self.u = Terminal() + self.u.user_wrapper = True + self.u.active_metadata = old_backend_data + + correct_backend = True + try: + fnull = open(os.devnull, 'w') + with redirect_stdout(fnull): + if backend_name.lower() == 'sqlite': + self.u.load_module('backend','Sqlite','back-write', filename=filename, **kwargs) + backend_name = 'Sqlite' + elif backend_name.lower() == 'duckdb': + self.u.load_module('backend','DuckDB','back-write', filename=filename, **kwargs) + backend_name = 'DuckDB' + else: + correct_backend = False + except Exception as e: + logger.error(f"process() ERROR: {e}", exc_info=True) + if e.args: + e.args = (f'process() ERROR: {str(e.args[0])}',) + e.args[1:] + raise + + if not correct_backend: + raise RuntimeError("Please check the 'backend_name' argument as that one is not supported by DSI\n" + "Eligible backend_names are: Sqlite, DuckDB") + + try: + self.u.artifact_handler(interaction_type='ingest') + except Exception as e: + if e.args: + e.args = (f'process() ERROR: {str(e.args[0])}',) + e.args[1:] + raise + + try: + fnull = open(os.devnull, 'w') + with redirect_stdout(fnull): + self.u.close() + self.t.unload_module('backend',backend_name,'back-write') + self.t.load_module('backend',backend_name,'back-write', filename=filename) + except Exception as e: # there really shouldn't be an error here + logger.error(f"process() ERROR: {e}", exc_info=True) + if e.args: + e.args = (f'process() ERROR: {str(e.args[0])}',) + e.args[1:] + raise + def list_writers(self): """ Prints a list of valid writers that can be used in the `writer_name` argument in `write()` From a53ac785e2346b46329f8314d39d19288bfd4313 Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Mon, 27 Apr 2026 14:11:19 -0600 Subject: [PATCH 087/109] catch stale mount error when creating remote dir in sync.copy() --- dsi/sync.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsi/sync.py b/dsi/sync.py index 568d8a3d..1ff142db 100644 --- a/dsi/sync.py +++ b/dsi/sync.py @@ -258,6 +258,8 @@ def copy(self, tool="copy", isVerbose=False, **kwargs): # os.makedirs(remote) # Create it print(f"The directory '{remote}' has been created remotely.") except Exception as err: + if "input/output error" in str(err).lower(): + raise RuntimeError(f"Environment error: Stale filesystem mount is not allowing write access to {remote}") raise RuntimeError(f"Error creating remote directory: {err}") # Future: have movement service handle type without user input (cp,scp,ftp,rsync,etc.) @@ -734,4 +736,4 @@ def copy(self, tool="copy", isVerbose=False, **kwargs): t.load_module('backend','HPSS', 'back-write', hpss_files=hpss_files) # Data movement via the hsi HPSS commands t.artifact_handler(interaction_type='ingest') - print( " Data Copy Complete! ") \ No newline at end of file + print( " Data Copy Complete! ") From a20516327b9b66f7008c1aeab766f5f90ca204ac Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Mon, 27 Apr 2026 14:56:11 -0600 Subject: [PATCH 088/109] Improve error handling for remote directory creation --- dsi/sync.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dsi/sync.py b/dsi/sync.py index 1ff142db..1ffdb47f 100644 --- a/dsi/sync.py +++ b/dsi/sync.py @@ -259,7 +259,12 @@ def copy(self, tool="copy", isVerbose=False, **kwargs): print(f"The directory '{remote}' has been created remotely.") except Exception as err: if "input/output error" in str(err).lower(): - raise RuntimeError(f"Environment error: Stale filesystem mount is not allowing write access to {remote}") + ls_result = subprocess.run(["ls", remote], stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, encoding='latin-1') + if ls_result.returncode != 0 and "stale file handle" in ls_result.stderr.lower(): + raise RuntimeError(f"Stale filesystem mount detected, cannot copy data to {remote}") + else: + raise RuntimeError(f"Input/Output error detected: {err}") raise RuntimeError(f"Error creating remote directory: {err}") # Future: have movement service handle type without user input (cp,scp,ftp,rsync,etc.) From ec5a0f897172ad8502f4f1d6ca3c5f928244a591 Mon Sep 17 00:00:00 2001 From: Divya Banesh Date: Tue, 28 Apr 2026 14:25:46 -0600 Subject: [PATCH 089/109] Add OSTI backend --- dsi/backends/osti.py | 889 +++++++++++++++++++++ dsi/cli.py | 6 + dsi/core.py | 18 +- dsi/dsi.py | 81 +- examples/osti/1.load_keywords.py | 16 + examples/osti/2.search_title.py | 17 + examples/osti/3.lookup_osti_id.py | 15 + examples/osti/4.fetch_report_identifier.py | 34 + examples/osti/5.fetch_reports.py | 20 + 9 files changed, 1092 insertions(+), 4 deletions(-) create mode 100644 dsi/backends/osti.py create mode 100644 examples/osti/1.load_keywords.py create mode 100644 examples/osti/2.search_title.py create mode 100644 examples/osti/3.lookup_osti_id.py create mode 100644 examples/osti/4.fetch_report_identifier.py create mode 100644 examples/osti/5.fetch_reports.py diff --git a/dsi/backends/osti.py b/dsi/backends/osti.py new file mode 100644 index 00000000..3d24c23b --- /dev/null +++ b/dsi/backends/osti.py @@ -0,0 +1,889 @@ +#!/usr/bin/env python3 +""" +OSTI Backend for DSI + +Read-only access that pulls metadata from REST-based OSTI backend +and exposes it as an in-memory DSI table: records +""" + +import requests +import pandas as pd +from urllib.parse import urlparse +from collections import OrderedDict + +from dsi.backends.webserver import Webserver + +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +# ---------------------------------------------------------------------- +# Value Object (used for search results) +# ---------------------------------------------------------------------- +class ValueObject: + """ + Container for search results returned by find* methods + + Attributes + ---------- + t_name : str + Table name + c_name : list + Column name(s) + row_num : int or None + Row index (if applicable) + value : any + Matched value + type : str + {'table', 'column', 'cell'} + """ + def __init__(self): + self.t_name = "" + self.c_name = [] + self.row_num = None + self.value = None + self.type = "" + +# --------------------------------------------------------- +# OSTI Backend (Webserver - read only) +# --------------------------------------------------------- +class OSTI(Webserver): + """ + REST-based web backend for querying OSTI metadata in-memory + """ + + # ---------------------------- + # Initialization + # ---------------------------- + def __init__(self, url=None, params=None, **kwargs): + """ + Initialize backend and optionally load data from REST API. + + Parameters + ---------- + + `url` : str, optional + Base OSTI URL. If None, a default OSTI endpoint is used. + `params` : dict, optional + Dictionary of initial query parameters used to fetch data from OSTI. + Supported keys: + "q", + "osti_id", + "doi", + "fulltext", + "biblio", + "author", + "title", + "identifier", + "sponsor_org", + "research_org", + "contributing_org", + "source_id", + "publication_date_start", + "publication_date_end", + "entry_date_start", + "entry_date_end", + "language", + "country", + "site_ownership_code", + "subject", + "has_fulltext", + "sort", + "order", + "rows", + "page", + `**kwargs` : dict + Additional keyword arguments: + - api_key : str, optional + API key for authentication + - verify_ssl : bool, optional + Toggle SSL verification (default False) + """ + + DEFAULT_URL = "https://www.osti.gov/api/v1" + base_url = url or DEFAULT_URL + + # ---------------------------------------------------------------------- + # Auth / connection config + # ---------------------------------------------------------------------- + self.api_key = kwargs.get("api_key") + self.verify_ssl = kwargs.get("verify_ssl", False) + + parsed = urlparse(base_url) + if not parsed.scheme or not parsed.netloc: + raise ValueError("Invalid base_url") + + self.base_url = base_url.rstrip("/") + + # OSTI public API does not require authentication, + # but we allow optional API key for future or private endpoints + self.headers = {} + if self.api_key: + self.headers["Authorization"] = self.api_key + + # In-memory storage (DSI format) + self._cache = OrderedDict() + + self._loaded = False + self.params = params or {} + + # Validate connection FIRST before attempting to load data + if not self.validate_connection(): + self._loaded = False + raise ConnectionError(f"Unable to connect to OSTI API at {self.base_url}") + + # Initial data load + if self.params: + try: + self._load_initial_data(self.params) + self._loaded = True # Data successfully loaded + except Exception as e: + self._loaded = False + raise RuntimeError(f"Failed to load initial data: {e}") from e + else: + self._loaded = True # Backend ready, no initial data to load + + # ---------------------------------------------------------------------- + # Connection Validation + # ---------------------------------------------------------------------- + def validate_connection(self): + """ + Validates that the base OSTI URL is accessible and functional. + + This method tests the connection by making a simple API call to verify: + - The URL is reachable + - The API responds with valid JSON + - The response format is as expected (list of records) + + Returns + ------- + bool + True if connection is valid, False otherwise + """ + try: + # test_url = f"{self.base_url}/records" + test_url = self.base_url + "/records" + + response = requests.get( + test_url, + headers=self.headers, + verify=self.verify_ssl, + timeout=2, + params={"rows": 1} # minimal request + + ) + response.raise_for_status() + + data = response.json() + + # OSTI returns a list of records for /records + if not isinstance(data, list): + return False + + return True + + except Exception: # noqa: E722 + # Silent failure to allow external workflows to continue + return False + + # --------------------------------------------------- + # Initial Data Load + # --------------------------------------------------- + def _load_initial_data(self, params): + """ + Fetch records from OSTI API and store in memory. + + Parameters + ---------- + params : dict + OSTI query parameters used with GET /records. + """ + + supported_params = [ + "q", + "osti_id", + "doi", + "fulltext", + "biblio", + "author", + "title", + "identifier", + "sponsor_org", + "research_org", + "contributing_org", + "source_id", + "publication_date_start", + "publication_date_end", + "entry_date_start", + "entry_date_end", + "language", + "country", + "site_ownership_code", + "subject", + "has_fulltext", + "sort", + "order", + "rows", + "page", + ] + + # Keep OSTI's defaults for rows/page unless caller overrides them. + request_params = { + "rows": params.get("rows", 20), + "page": params.get("page", 1), + } + + # Build request params from whichever values were provided. + for key in supported_params: + if key in ("rows", "page"): + continue + value = params.get(key) + if value is not None: + request_params[key] = value + + #### ALTERNATE TO USE BOTH ENDPOINTS: + # if "osti_id" in params and len(params) == 1: + # result = self._request(f"records/{params['osti_id']}") + # records = [result] # normalize to list + # else: + # result = self._request("records", request_params) + # records = result if isinstance(result, list) else [] + + # Call OSTI's record-list endpoint: GET /records + # Note: this does not use the single-record /records/{osti_id} endpoint. + result = self._request("records", request_params) + + # The OSTI record-list response is a list of record objects + # One more check to see if any bad results produced + # Validate response shape defensively. + records = result if isinstance(result, list) else [] + + record_rows = self._extract_tables(records) + self._cache["records"] = self._rows_to_table(record_rows) + + self._loaded = True + +# --------------------------------------------------- +# API Helpers +# --------------------------------------------------- + def _request(self, endpoint, params=None): + """ + Execute GET request against OSTI API. + + Parameters + ---------- + `endpoint` : str + OSTI API endpoint name + `params` : dict, optional + Query parameters for the request + + Returns + ------- + object + Parsed JSON response from the OSTI API. + """ + url = f"{self.base_url}/{endpoint.lstrip('/')}" + + try: + response = requests.get( + url, + params=params or {}, + headers=self.headers, + verify=self.verify_ssl, + timeout=30 + ) + response.raise_for_status() + return response.json() + + except requests.exceptions.HTTPError as e: + self._loaded = False + raise RuntimeError(f"HTTP error while calling OSTI: {e}") from e + + except requests.exceptions.RequestException as e: + self._loaded = False + raise RuntimeError(f"Request failed: {e}") from e + + except ValueError as e: + self._loaded = False + raise RuntimeError(f"Invalid JSON response: {e}") from e + + + def _flatten_list(self, values, key=None): + """ + Flatten a list of values (strings or dicts) into a semicolon-separated string. + """ + + if not values: + return "" + + flattened = [] + + for item in values: + if isinstance(item, dict): + value = item.get(key) if key else None + if value is None: + value = item.get("name") or item.get("value") or str(item) + else: + value = item + + if value is not None: + flattened.append(str(value)) + + return "; ".join(flattened) + + + def _extract_tables(self, records): + """ + Flatten OSTI dataset JSON into records tables. + + Parameters + ---------- + `records` : list + List of dataset records from OSTI API + + Returns + ------- + 'record_rows' : list of dict flattened dataset metadata + + """ + + record_rows = [] + + for rec in records: + authors = rec.get("authors", []) or [] + subjects = rec.get("subjects", []) or [] + links = rec.get("links", []) or [] + + citation_url = None + doe_pages_url = None + fulltext_url = None + + for link in links: + if isinstance(link, dict): + rel = link.get("rel") + href = link.get("href") + + if rel == "citation": + citation_url = href + elif rel == "citation_doe_pages": + doe_pages_url = href + elif rel == "fulltext": + fulltext_url = href + + record_rows.append({ + "osti_id": rec.get("osti_id"), + "title": rec.get("title"), + "doi": rec.get("doi"), + "publication_date": rec.get("publication_date"), + "entry_date": rec.get("entry_date"), + "language": rec.get("language"), + "country_publication": rec.get("country_publication"), + "site_ownership_code": rec.get("site_ownership_code"), + "product_type": rec.get("product_type"), + "description": rec.get("description"), + "publisher": rec.get("publisher"), + "journal_name": rec.get("journal_name"), + "journal_volume": rec.get("journal_volume"), + "journal_issue": rec.get("journal_issue"), + "journal_page": rec.get("journal_page"), + "conference_name": rec.get("conference_name"), + "conference_location": rec.get("conference_location"), + "conference_date": rec.get("conference_date"), + "report_number": rec.get("report_number"), + "contract_number": rec.get("contract_number") or rec.get("doe_contract_number"), + "source_id": rec.get("source_id"), + "source_osti_id": rec.get("source_osti_id"), + + "authors": self._flatten_list(authors, key="name"), + "subjects": self._flatten_list(subjects), + "sponsor_orgs": self._flatten_list(rec.get("sponsor_orgs", []) or [], key="name"), + "research_orgs": self._flatten_list(rec.get("research_orgs", []) or [], key="name"), + "other_identifiers": self._flatten_list(rec.get("other_identifiers", []) or []), + + "has_fulltext": fulltext_url is not None, + "citation_url": citation_url, + "citation_doe_pages_url": doe_pages_url, + "fulltext_url": fulltext_url, + + "raw_links": links, + "raw_record": rec, + }) + + return record_rows + + + def _rows_to_table(self, rows): + """ + Convert list-of-dicts to column-oriented OrderedDict. + + Parameters + ---------- + `rows` : list of dict + Row data as list of dictionaries + + Returns + ------- + OrderedDict + Column-oriented table structure + """ + + if not rows: + return OrderedDict() + + # Preserve column order while capturing all keys + cols = list(rows[0].keys()) + for r in rows[1:]: + for k in r.keys(): + if k not in cols: + cols.append(k) + + table = OrderedDict({c: [] for c in cols}) + + for r in rows: + for c in cols: + table[c].append(r.get(c)) + + return table + + # ---------------------------------------------------------------------- + # Terminal Methods + # ---------------------------------------------------------------------- + def get_table(self, table_name="records", dict_return=False): + """ + Returns all data from the records table. + + Parameters + ---------- + `table_name` : str + Must be "records" + `dict_return` : bool, default False + If True, returns OrderedDict. If False, returns DataFrame. + + Returns + ------- + OrderedDict or pandas.DataFrame + """ + if table_name != "records": + raise ValueError("OSTI backend only contains the 'records' table") + + if "records" not in self._cache: + raise ValueError("No OSTI records loaded") + + table = self._cache["records"] + + if dict_return: + return table + + return pd.DataFrame(table) + + def get_schema(self): + """OSTI does not store structural schema - data comes from OSTI API.""" + return ( + "-- OSTI Backend Schema Information\n" + "-- OSTI is a read-only REST metadata backend\n" + "-- Data is retrieved dynamically from the API\n" + "-- Use summary() or list() to view available tables and columns\n" + ) + + def overwrite_table(self, table_name, collection): + """ + Not supported - OSTI backend is read-only. + + Parameters + ---------- + `table_name` : str or list + Table name(s) + `collection` : DataFrame or list + Data + + Raises + ------ + NotImplementedError + Always raised as OSTI is read-only + """ + raise NotImplementedError( + "OSTI backend is read-only. Cannot overwrite tables. " + "To modify data, use artifact_handler('process') to load into " + "a writable backend (Sqlite/DuckDB), make changes, then query." + ) + + # --------------------------------------------------- + # Query Interface (in-memory) + # --------------------------------------------------- + def query_artifacts(self, query, dict_return=True, **kwargs): + """ + Query the cached records table using pandas.query(). + + Parameters + ---------- + query : str + Pandas query string for filtering data. + dict_return : bool, default True + If True, returns OrderedDict format. + If False, returns a pandas DataFrame. + **kwargs : dict + Additional keyword arguments. + + Returns + ------- + OrderedDict or pandas.DataFrame + Query results from the records table. + """ + if not self._loaded: + raise RuntimeError("No metadata loaded. Cannot query empty backend.") + + if "records" not in self._cache: + raise RuntimeError("No records table loaded.") + + df = pd.DataFrame(self._cache["records"]) + + if df.empty: + raise ValueError(f"Query returned no results: '{query}'") + + try: + result_df = df.query(query, engine="python") + + if result_df.empty: + raise ValueError(f"Query returned no results: '{query}'") + + if dict_return: + return OrderedDict(result_df.to_dict(orient="list")) + + return result_df + + except pd.errors.UndefinedVariableError: + raise ValueError(f"Query references an unknown column: '{query}'") from None + + except ValueError: + raise + + except Exception as e: + raise ValueError(f"Query error in records: {e}") from e + + # ---------------------------------------------------------------------- + # Artifact Processing (tiered table construction) + # ---------------------------------------------------------------------- + def process_artifacts(self): + """ + Returns all cached OSTI data. + + Structure: + { + "records": + } + + Useful for exporting or writing to external systems. + + Returns + ------- + OrderedDict + Cached records table + """ + if not self._loaded: + return {} + + return self._cache + + + # --------------------------------------------------- + # URL Validation + # --------------------------------------------------- + def validate_urls(self): + """ + Validate URL fields in the records table. + + Adds boolean columns indicating whether each URL is reachable: + - citation_url_valid + - citation_doe_pages_url_valid + - fulltext_url_valid + + Returns + ------- + None + """ + if not self._loaded: + raise RuntimeError("No data loaded. Cannot validate URLs.") + + if "records" not in self._cache: + raise RuntimeError("No records table loaded.") + + df = pd.DataFrame(self._cache["records"]) + + if df.empty: + return + + url_fields = [ + "citation_url", + "citation_doe_pages_url", + "fulltext_url", + ] + + for field in url_fields: + if field not in df.columns: + continue + + validity = [] + + for url in df[field]: + if not url: + validity.append(False) + continue + + try: + response = requests.head( + url, + allow_redirects=True, + timeout=5, + verify=self.verify_ssl, + ) + + # Some servers block HEAD → fallback to GET + if response.status_code == 405: + response = requests.get( + url, + timeout=5, + verify=self.verify_ssl, + ) + + validity.append(200 <= response.status_code < 400) + + except Exception: + validity.append(False) + + df[f"{field}_valid"] = validity + + # write back to cache + self._cache["records"] = df.to_dict(orient="list") + + + # ---------------------------------------------------------------------- + # Find Methods + # ---------------------------------------------------------------------- + def find(self, query_object, **kwargs): + """ + Search for query_object across table names, column names, and cell values. + """ + if not self._loaded: + return [] + + query_str = str(query_object).lower() + + return ( + self.find_table(query_str) + + self.find_column(query_str) + + self.find_cell(query_object) + ) + + + def find_table(self, query_object, **kwargs): + """ + Find tables whose names contain query_object. + """ + if not self._loaded or not isinstance(query_object, str): + return [] + + matches = [] + + for table_name, table_data in self._cache.items(): + if query_object.lower() in table_name.lower(): + val = ValueObject() + val.t_name = table_name + val.c_name = list(table_data.keys()) + val.value = table_data + val.type = "table" + matches.append(val) + + return matches + + + def find_column(self, query_object, **kwargs): + """ + Find columns whose names contain query_object. + """ + if not self._loaded or not isinstance(query_object, str): + return [] + + matches = [] + + for table_name, table_data in self._cache.items(): + for col_name, col_data in table_data.items(): + if query_object.lower() in col_name.lower(): + val = ValueObject() + val.t_name = table_name + val.c_name = [col_name] + val.value = col_data + val.type = "column" + matches.append(val) + + return matches + + + def find_cell(self, query_object, **kwargs): + """ + Find cells that match query_object. + + Matching behavior: + - Exact match for all data types + - Case-insensitive partial match for strings + """ + if not self._loaded: + return [] + + matches = [] + + is_str_query = isinstance(query_object, str) + query_lower = query_object.lower() if is_str_query else None + + for table_name, table_data in self._cache.items(): + if not table_data: + continue + + cols = list(table_data.keys()) + rows = zip(*table_data.values()) + + for row_idx, row in enumerate(rows): + for col_idx, cell in enumerate(row): + match = False + + if query_object == cell: + match = True + + elif ( + is_str_query + and isinstance(cell, str) + and query_lower in cell.lower() + ): + match = True + + if match: + val = ValueObject() + val.t_name = table_name + val.c_name = [cols[col_idx]] + val.row_num = row_idx + val.value = cell + val.type = "cell" + matches.append(val) + + return matches + + + def find_relation(self, column_name, relation, **kwargs): + """ + Relation finding is not supported for the OSTI backend. + """ + return [] + + # ---------------------------------------------------------------------- + # Utility / Display + # ---------------------------------------------------------------------- + def list(self, collection=False): + """ + List cached OSTI tables. + + Parameters + ---------- + collection : bool, default False + If True, return table names. + If False, print table names with dimensions. + """ + if collection: + return self._cache.keys() + + for name, table in self._cache.items(): + df = pd.DataFrame(table) + print(f"{name}: ({len(df)} rows, {len(df.columns)} cols)") + + + def summary(self, table_name=None): + """ + Return metadata summary for the cached records table. + """ + if not self._loaded or "records" not in self._cache: + return pd.DataFrame() + + if table_name and table_name != "records": + raise ValueError("OSTI backend only contains the 'records' table") + + df = pd.DataFrame(self._cache["records"]) + + summary_dict = { + "table_name": "records", + "num_rows": len(df), + "num_columns": len(df.columns), + "columns": list(df.columns), + } + + summary_df = pd.DataFrame([summary_dict]) + + if table_name: + return summary_df + + return [["records"], summary_df] + + + def display(self, table_name="records", num_rows=25, display_cols=None): + """ + Display rows from the records table. + """ + if not self._loaded: + raise RuntimeError("No data loaded. Cannot display empty backend.") + + if table_name != "records": + raise ValueError("OSTI backend only contains the 'records' table") + + if "records" not in self._cache: + raise ValueError("No OSTI records loaded") + + df = pd.DataFrame(self._cache["records"]) + + if df.empty: + raise ValueError("The records table is empty") + + if display_cols: + missing_cols = set(display_cols) - set(df.columns) + if missing_cols: + raise ValueError( + f"Columns not found in 'records': {missing_cols}\n" + f"Available columns: {list(df.columns)}" + ) + df = df[display_cols] + + df.attrs["max_rows"] = len(df) + + if num_rows: + df = df.head(num_rows) + + return df.map( + lambda x: ( + str(x)[:60] + "..." + if isinstance(x, str) and len(str(x)) > 60 + else x + ) + ) + + + def notebook(self, **kwargs): + """ + Notebook generation is not supported for the OSTI backend. + """ + pass + + + # ---------------------------------------------------------------------- + # Lifecycle + # ---------------------------------------------------------------------- + def close(self): + """ + Reset backend state and clear cached data. + """ + self._cache = OrderedDict() + self._loaded = False + + + # ---------------------------------------------------------------------- + # Abstract Methods + # ---------------------------------------------------------------------- + def ingest_artifacts(self, artifacts, **kwargs) -> None: + """ + Ingest is not supported for the OSTI backend. + """ + raise NotImplementedError("OSTI backend is read-only") \ No newline at end of file diff --git a/dsi/cli.py b/dsi/cli.py index 105b0258..a1bef19c 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -115,6 +115,12 @@ def startup(self, backend="sqlite"): # NDP requires params dict for initialization self.t.load_module('backend', 'NDP', 'back-read', params={"keywords": keywords}) self.name = "ndp" + elif backend.lower() == "osti": + # PLACEHOLDER for future implement + # OSTI requires params dict for initialization + print("OSTI backend requires configuration.") + print("Use Python API: t.load_module('backend', 'OSTI', 'back-read', params={...})") + self.exit_cli([]) else: backend = "sqlite" self.t.load_module('backend','Sqlite','back-write', filename = self.db_path) diff --git a/dsi/core.py b/dsi/core.py index 22e0848f..119e2037 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -26,7 +26,7 @@ class Terminal(): for more information. """ BACKEND_PREFIX = ['dsi.backends'] - BACKEND_IMPLEMENTATIONS = ['gufi', 'sqlite', 'duckdb', 'hpss', 'ndp'] + BACKEND_IMPLEMENTATIONS = ['gufi', 'sqlite', 'duckdb', 'hpss', 'ndp', 'osti'] PLUGIN_PREFIX = ['dsi.plugins'] PLUGIN_IMPLEMENTATIONS = ['env', 'file_reader', 'file_writer', 'collection_reader'] VALID_ENV = ['Hostname', 'SystemKernel', 'GitInfo'] @@ -34,7 +34,7 @@ class Terminal(): VALID_DATACARDS = ['Oceans11Datacard', 'DublinCoreDatacard', 'SchemaOrgDatacard', 'GoogleDatacard', 'GenesisDatacard'] VALID_WRITERS = ['ER_Diagram', 'Table_Plot', 'Csv_Writer', 'Parquet_Writer'] VALID_PLUGINS = VALID_ENV + VALID_READERS + VALID_WRITERS + VALID_DATACARDS - VALID_BACKENDS = ['Gufi', 'Sqlite', 'DuckDB', 'SqlAlchemy', 'HPSS', 'NDP'] + VALID_BACKENDS = ['Gufi', 'Sqlite', 'DuckDB', 'SqlAlchemy', 'HPSS', 'NDP', 'OSTI'] VALID_MODULES = VALID_PLUGINS + VALID_BACKENDS VALID_MODULE_FUNCTIONS = {'plugin': ['reader', 'writer'], 'backend': ['back-read', 'back-write']} @@ -1410,6 +1410,20 @@ def valid_backend(self, backend, parent_name): f"NDP backend connection validation failed: {str(e)}" ) return False + if backend.__class__.__name__ == "OSTI": + # OSTI is valid if data is loaded and connection works + if not backend._loaded: + return False + + try: + backend.validate_connection() + return True + except (ConnectionError, RuntimeError) as e: + if self.debug_level != 0: + self.logger.warning( + f"OSTI backend connection validation failed: {str(e)}" + ) + return False return False # Internal function that returns if a user can create a file/db in a specified location diff --git a/dsi/dsi.py b/dsi/dsi.py index 946cbe46..7c86ab20 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -1,5 +1,6 @@ from dsi.core import Terminal #, Sync from dsi.backends.ndp import NDP +from dsi.backends.osti import OSTI from collections import OrderedDict import numpy as np import pandas as pd @@ -39,11 +40,12 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) - If backend_name = "Sqlite" → .db, .sqlite, .sqlite3 - If backend_name = "DuckDB" → .duckdb, .db - If backend_name = "NDP" → No file required (read-only backend) + - If backend_name = "OSTI" → No file required (read-only backend) `backend_name` : str, optional, default is "Sqlite". Name of the backend to activate. - If using a DSI-supported backend, must be either "Sqlite", "DuckDB", or "NDP". + If using a DSI-supported backend, must be either "Sqlite", "DuckDB", "NDP" or "OSTI". If using an external backend, provide the relative path to the Python module with the backend. """ @@ -66,6 +68,17 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if filename == ".temp_dsi.db" and os.path.exists(filename): os.remove(filename) + # Skip file creation checks for OSTI (read-only backend) + if backend_name.lower() != "osti": + if "/" in filename: + create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) + else: + create_bool = self.t.can_create_file_here() + if create_bool is False: + raise RuntimeError("Cannot initialize DSI due to write permissions in this directory. Please try elsewhere.") + + if filename == ".temp_dsi.db" and os.path.exists(filename): + os.remove(filename) if backend_name.endswith(".py"): if not os.path.exists(backend_name): @@ -135,6 +148,59 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if e.args: e.args = (f'backend ERROR: {str(e.args[0])}',) + e.args[1:] raise + + # Handle OSTI separately (read-only backend) + elif backend_name.lower() == "osti": + self.database_name = None # OSTI doesn't use a file + + correct_backend = True + + # Extract OSTI query parameters from kwargs + query_params = {} + + if "params" in kwargs: + query_params.update(kwargs.pop("params")) + + osti_param_keys = [ + "q", + "osti_id", + "doi", + "fulltext", + "biblio", + "author", + "title", + "identifier", + "sponsor_org", + "research_org", + "contributing_org", + "source_id", + "publication_date_start", + "publication_date_end", + "entry_date_start", + "entry_date_end", + "language", + "country", + "site_ownership_code", + "subject", + "has_fulltext", + "sort", + "order", + "rows", + "page", + ] + + for key in osti_param_keys: + if key in kwargs: + query_params[key] = kwargs.pop(key) # Remove from kwargs after extraction + + try: + # Pass query params as 'params' argument + self.t.load_module('backend', 'OSTI', 'back-read', params=query_params, **kwargs) + except Exception as e: + logger.error(f"backend ERROR: {e}", exc_info=True) + if e.args: + e.args = (f'backend ERROR: {str(e.args[0])}',) + e.args[1:] + raise # Handle file-based backends (Sqlite, DuckDB) else: @@ -164,12 +230,14 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if not correct_backend: raise RuntimeError("Please check the 'backend_name' argument as that one is not supported by DSI\n" - "Eligible backend_names are: Sqlite, DuckDB, NDP") + "Eligible backend_names are: Sqlite, DuckDB, NDP, OSTI") self.main_backend_obj = self.t.loaded_backends[0] if backend_name.lower() == "ndp": msg = "Created an instance of DSI with the NDP read-only backend" + if backend_name.lower() == "osti": + msg = "Created an instance of DSI with the OSTI read-only backend" elif filename != ".temp_dsi.db": msg = f"Created an instance of DSI with the {backend_name} backend: {filename}" else: @@ -188,6 +256,9 @@ def list_backends(self): n = NDP() if n.validate_connection(): print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.\n") + n = OSTI() + if n.validate_connection(): + print("OSTI : Read-only data catalog backend for discovering and querying OSTI (REST-based) open data resources.\n") print() def schema(self, filename = None): @@ -203,6 +274,8 @@ def schema(self, filename = None): """ if self.main_backend_obj.__class__.__name__ == "NDP": raise RuntimeError("schema() ERROR: NDP is a read-only backend and does not support schema operations.") + if self.main_backend_obj.__class__.__name__ == "OSTI": + raise RuntimeError("schema() ERROR: OSTI is a read-only backend and does not support schema operations.") if filename: if not os.path.exists(filename): raise RuntimeError("schema() ERROR: Input schema file must have a valid filepath. Please check again.") @@ -304,6 +377,8 @@ def read(self, data_sources: str | list[str] | dict | pd.DataFrame | None = None """ if self.main_backend_obj.__class__.__name__ == "NDP": raise RuntimeError("read() ERROR: NDP is a read-only backend. Data cannot be added.") + if self.main_backend_obj.__class__.__name__ == "OSTI": + raise RuntimeError("read() ERROR: OSTI is a read-only backend. Data cannot be added.") # only DSI-repo readers require data_sources input. Custom readers do not. if isinstance(data_sources, str) and not os.path.exists(data_sources) and not reader_name.endswith(".py"): raise RuntimeError("read() ERROR: The input file must be a valid filepath. Please check again.") @@ -748,6 +823,8 @@ def update(self, collection, backup = False): """ if self.main_backend_obj.__class__.__name__ == "NDP": raise RuntimeError("update() ERROR: NDP is a read-only backend. Data cannot be updated.") + if self.main_backend_obj.__class__.__name__ == "OSTI": + raise RuntimeError("update() ERROR: OSTI is a read-only backend. Data cannot be updated.") if not self.t.valid_backend(self.main_backend_obj, self.main_backend_obj.__class__.__bases__[0].__name__): raise RuntimeError("ERROR: Cannot update() an empty backend. Please ensure there is data in it.") diff --git a/examples/osti/1.load_keywords.py b/examples/osti/1.load_keywords.py new file mode 100644 index 00000000..16bcde74 --- /dev/null +++ b/examples/osti/1.load_keywords.py @@ -0,0 +1,16 @@ +from dsi.dsi import DSI + +# load OSTI backend and query for 5 entries with the keyword climate +# both formats work +#dsi = DSI(backend_name="OSTI", params={"q": "climate","rows": 5}) +dsi = DSI(backend_name="OSTI", q="climate", rows=5) + +# list the number of resulting entries +dsi.list() + +# list the corresponding records +df = dsi.get_table("records", collection=True) +# print the metadata fields: osti_id, title and publication_date +print(df[["osti_id", "title", "publication_date"]].head()) + +dsi.close() diff --git a/examples/osti/2.search_title.py b/examples/osti/2.search_title.py new file mode 100644 index 00000000..57524f21 --- /dev/null +++ b/examples/osti/2.search_title.py @@ -0,0 +1,17 @@ +# examples/osti/osti_user/2.search_title.py + +from dsi.dsi import DSI + +# Search OSTI records by title field +dsi = DSI(backend_name="OSTI", title="climate model", rows=10,) + +df = dsi.get_table("records", collection=True) + +print("\nRecords matching title search:") +print(df[["osti_id", "title", "publication_date"]].head()) + +dsi.summary() + +dsi.close() + + diff --git a/examples/osti/3.lookup_osti_id.py b/examples/osti/3.lookup_osti_id.py new file mode 100644 index 00000000..87341ae3 --- /dev/null +++ b/examples/osti/3.lookup_osti_id.py @@ -0,0 +1,15 @@ +from dsi.dsi import DSI + +# Query OSTI records using osti_id through GET /records?osti_id=... +dsi = DSI(backend_name="OSTI", osti_id="1234567",rows=5) + +df = dsi.get_table("records", collection=True) + +print("\nOSTI ID query results:") +print(df[["osti_id", "title", "doi", "publication_date"]].head()) + +print("\nAvailable columns:") +print(df.columns.tolist()) + +dsi.close() + diff --git a/examples/osti/4.fetch_report_identifier.py b/examples/osti/4.fetch_report_identifier.py new file mode 100644 index 00000000..76c63e3c --- /dev/null +++ b/examples/osti/4.fetch_report_identifier.py @@ -0,0 +1,34 @@ +from dsi.dsi import DSI +import pandas as pd + +report_numbers = [ + "LA-UR-24-12345", + "LA-UR-24-23456", + "LA-UR-24-34567", +] + +frames = [] + +for rn in report_numbers: + dsi = DSI( + backend_name="OSTI",identifier=rn,rows=10,silence_messages=True, + ) + + df = dsi.get_table("records", collection=True) + + if df is not None and not df.empty: + df = df[df["report_number"] == rn] + + if not df.empty: + df["queried_report_number"] = rn + frames.append(df) + + dsi.close() + +# Combine results +if frames: + results = pd.concat(frames, ignore_index=True) + + print(results[[ "queried_report_number", "report_number", "osti_id", "title", "publication_date"]]) +else: + print("No matching reports found.") diff --git a/examples/osti/5.fetch_reports.py b/examples/osti/5.fetch_reports.py new file mode 100644 index 00000000..7a572461 --- /dev/null +++ b/examples/osti/5.fetch_reports.py @@ -0,0 +1,20 @@ +from dsi.dsi import DSI + +report_numbers = [ + "LA-UR-25-31939", + "LA-UR-25-28633", + "LA-UR-25-29620" +] + +query = " OR ".join(f'"{rn}"' for rn in report_numbers) + +dsi = DSI( backend_name="OSTI", q=query, rows=100) + +df = dsi.get_table("records", collection=True) + +# enforce exact report-number matches locally +results = df[df["report_number"].isin(report_numbers)] + +print(results[["report_number", "osti_id", "title", "publication_date"]]) + +dsi.close() From abcbe76b2fcd0d05614cb1a8cc0dee57fa9807eb Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Tue, 28 Apr 2026 15:54:27 -0600 Subject: [PATCH 090/109] fixed print spacing --- dsi/dsi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index 7c86ab20..315b8541 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -255,7 +255,7 @@ def list_backends(self): print("DuckDB : In-process SQL backend optimized for fast analytics on large datasets.") n = NDP() if n.validate_connection(): - print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.\n") + print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.") n = OSTI() if n.validate_connection(): print("OSTI : Read-only data catalog backend for discovering and querying OSTI (REST-based) open data resources.\n") From 42b2e1a187125c3fc59cc2d0f393625b76a5c2d3 Mon Sep 17 00:00:00 2001 From: Vedant1 Date: Tue, 28 Apr 2026 16:20:28 -0600 Subject: [PATCH 091/109] updated file creation skip for osti and ndp backends in user api --- dsi/dsi.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index 315b8541..f32316ee 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -57,8 +57,8 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) self.silence_messages = kwargs.pop('silence_messages', False) - # Skip file creation checks for NDP (read-only backend) - if backend_name.lower() != "ndp": + # Skip file creation checks for NDP and OSTI (read-only backends) + if backend_name.lower() not in ["ndp", "osti"]: if "/" in filename: create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) else: @@ -68,18 +68,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if filename == ".temp_dsi.db" and os.path.exists(filename): os.remove(filename) - # Skip file creation checks for OSTI (read-only backend) - if backend_name.lower() != "osti": - if "/" in filename: - create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) - else: - create_bool = self.t.can_create_file_here() - if create_bool is False: - raise RuntimeError("Cannot initialize DSI due to write permissions in this directory. Please try elsewhere.") - - if filename == ".temp_dsi.db" and os.path.exists(filename): - os.remove(filename) - + if backend_name.endswith(".py"): if not os.path.exists(backend_name): raise RuntimeError("backend() ERROR: `backend_name` must be a valid filepath to the custom backend. Please check again.") From 4ad68f44dcf2564b25dea7ae0c90acc7a8dbdb04 Mon Sep 17 00:00:00 2001 From: Vedant1 Date: Tue, 28 Apr 2026 16:39:37 -0600 Subject: [PATCH 092/109] updated sync package load in diana tutorial --- examples/dsi_diana_tutorial.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dsi_diana_tutorial.ipynb b/examples/dsi_diana_tutorial.ipynb index 0f5bf564..77806ec7 100644 --- a/examples/dsi_diana_tutorial.ipynb +++ b/examples/dsi_diana_tutorial.ipynb @@ -494,7 +494,7 @@ "metadata": {}, "outputs": [], "source": [ - "from dsi.core import Sync" + "from dsi.sync import Sync" ] }, { From 7580261380c694e9d167e6bc7fab2e6f97d10086 Mon Sep 17 00:00:00 2001 From: Vedant1 Date: Tue, 28 Apr 2026 21:21:28 -0600 Subject: [PATCH 093/109] updated cli startup print message --- dsi/cli.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dsi/cli.py b/dsi/cli.py index a1bef19c..9ef3f5bd 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -111,25 +111,28 @@ def startup(self, backend="sqlite"): if backend=="duckdb": self.t.load_module('backend','DuckDB','back-write', filename = self.db_path) self.name = "duckdb" + backend = "DuckDB" elif backend.lower() == "ndp": # NDP requires params dict for initialization self.t.load_module('backend', 'NDP', 'back-read', params={"keywords": keywords}) self.name = "ndp" + backend = "NDP" elif backend.lower() == "osti": # PLACEHOLDER for future implement # OSTI requires params dict for initialization print("OSTI backend requires configuration.") print("Use Python API: t.load_module('backend', 'OSTI', 'back-read', params={...})") - self.exit_cli([]) + backend = "OSTI" + self.exit_cli([]) else: - backend = "sqlite" + backend = "Sqlite" self.t.load_module('backend','Sqlite','back-write', filename = self.db_path) self.name = "sqlite" except Exception as e: print(f"backend ERROR: {e}") with redirect_stdout(fnull): self.exit_cli([]) - print(f"Created a temporary {backend.capitalize()} DSI backend") + print(f"Created a temporary {backend} DSI backend") def help_fn(self, args): From e75073d9be6d276f04b637641dc154d82c1b6270 Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Wed, 29 Apr 2026 06:43:15 -0600 Subject: [PATCH 094/109] changed clover3d dc to dublin core --- examples/clover3d/dublin_core_dc.xml | 22 +++ examples/clover3d/genesis_datacard.xlsx | Bin 13641 -> 0 bytes examples/dsi_diana_tutorial.ipynb | 187 ++++++++++++++++++++++-- 3 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 examples/clover3d/dublin_core_dc.xml delete mode 100644 examples/clover3d/genesis_datacard.xlsx diff --git a/examples/clover3d/dublin_core_dc.xml b/examples/clover3d/dublin_core_dc.xml new file mode 100644 index 00000000..ce50e968 --- /dev/null +++ b/examples/clover3d/dublin_core_dc.xml @@ -0,0 +1,22 @@ + + Jesus Pulido + Jesus Pulido + DOE:LANL + Cloverleaf 3D Ensemble Dataset + 2025-10-09 + en + text/xml + Physics + This ensemble run of 8 runs is from the Cloverleaf3D simulation code, a Lagrangian-Eulerian hydrodynamics solver. More information about the code can be found here https://github.com/UK-MAC/CloverLeaf3D + ID-1234 + + + + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. http://fsf.org/ + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + \ No newline at end of file diff --git a/examples/clover3d/genesis_datacard.xlsx b/examples/clover3d/genesis_datacard.xlsx deleted file mode 100644 index de2cd1df495876177f89711d96be27b9bafa21e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13641 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%0D#SVr|AE$T>Rb7?*g{J zJY~cG-z5G`&i1>P-xtyU@?!YjuzH`(?@Q{xJNUgh|Ca+<{68K1)vEv9^!M)AU#3vR ze>45PSN6My-%FFfJdDu(eh>a*q4K-KexW(e>}<|C(C=**uNmpUnTB ZXceR(-oxdOFEQW%lkXLQKJy>H{vQMZvWfrz diff --git a/examples/dsi_diana_tutorial.ipynb b/examples/dsi_diana_tutorial.ipynb index 77806ec7..4d6da509 100644 --- a/examples/dsi_diana_tutorial.ipynb +++ b/examples/dsi_diana_tutorial.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "0fe6f810-9b2d-4369-9e46-2d78c96e2789", "metadata": {}, "outputs": [], @@ -32,10 +32,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "14e76c45-9449-4fcc-bdbd-6c287a430f37", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created an instance of DSI\n" + ] + } + ], "source": [ "# Create instance of DSI\n", "baseline = DSI()" @@ -66,10 +74,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "14b7ee57-da21-4c6c-86de-bf358907955e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from IPython.display import HTML\n", "\n", @@ -95,13 +121,23 @@ "execution_count": null, "id": "ffd67bca-2b0a-4eb0-bf56-12d09a2baee3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created an instance of DSI with the Sqlite backend: dsi-diana-tutorial.db\n", + "Loaded clover3d/clover3d_dublin_core.xml into the tables: dublin_core_datacard\n", + "Loaded ./clover3d/ into the tables: input, output, simulation, viz_files\n" + ] + } + ], "source": [ "# Target backend defaults to SQLite since not defined\n", "store = DSI(\"dsi-diana-tutorial.db\")\n", "\n", "# Read in Datacard (Tier 1)\n", - "store.read(\"clover3d/genesis_datacard.xlsx\", 'GenesisDatacard')\n", + "store.read(\"clover3d/dublin_core_dc.xml\", 'DublinCoreDatacard')\n", "\n", "# Read in Tier 2 and Tier 3 metadata\n", "# dsi.read(path, reader)\n", @@ -118,10 +154,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "b911a426-a0f2-45a5-94bc-a71fce55f7bd", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Database now has 5 tables\n" + ] + } + ], "source": [ "# How many tables do we have\n", "store.num_tables()" @@ -129,10 +173,39 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "ec76764e-e25e-4720-8cc2-d7eab8d59363", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Table: dublin_core_datacard\n", + " - num of columns: 15\n", + " - num of rows: 1\n", + "\n", + "Table: input\n", + " - num of columns: 22\n", + " - num of rows: 8\n", + "\n", + "Table: output\n", + " - num of columns: 10\n", + " - num of rows: 720\n", + "\n", + "Table: simulation\n", + " - num of columns: 2\n", + " - num of rows: 8\n", + "\n", + "Table: viz_files\n", + " - num of columns: 2\n", + " - num of rows: 80\n", + "\n", + "\n" + ] + } + ], "source": [ "# Let's see what tables were created\n", "store.list()" @@ -140,10 +213,94 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "146d4e66-57df-48f5-ba2e-0c438668310c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Table: dublin_core_datacard\n", + "\n", + "column | type | unique | min | max | avg | std_dev\n", + "-------------------------------------------------------------\n", + "Creator | VARCHAR | 1 | None | None | None | None \n", + "Contributor | VARCHAR | 1 | None | None | None | None \n", + "Publisher | VARCHAR | 1 | None | None | None | None \n", + "Title | VARCHAR | 1 | None | None | None | None \n", + "Date | VARCHAR | 1 | None | None | None | None \n", + "Language | VARCHAR | 1 | None | None | None | None \n", + "Format | VARCHAR | 1 | None | None | None | None \n", + "Subject | VARCHAR | 1 | None | None | None | None \n", + "Description | VARCHAR | 1 | None | None | None | None \n", + "Identifier | VARCHAR | 1 | None | None | None | None \n", + "Relation | VARCHAR | 1 | None | None | None | None \n", + "Source | VARCHAR | 1 | None | None | None | None \n", + "Type | VARCHAR | 1 | None | None | None | None \n", + "Coverage | VARCHAR | 1 | None | None | None | None \n", + "Rights | VARCHAR | 1 | None | None | None | None \n", + "\n", + "Table: input\n", + "\n", + "column | type | unique | min | max | avg | std_dev \n", + "------------------------------------------------------------------------------------------------\n", + "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792 \n", + "state1_density | FLOAT | 1 | 0.2 | 0.2 | 0.19999999999999998 | 2.7755575615628914e-17\n", + "state1_energy | FLOAT | 1 | 1.0 | 1.0 | 1.0 | 0.0 \n", + "state2_density | FLOAT | 8 | 2.0 | 9.0 | 5.5 | 2.29128784747792 \n", + "state2_energy | FLOAT | 1 | 2.5 | 2.5 | 2.5 | 0.0 \n", + "state2_geometry | VARCHAR | 1 | None | None | None | None \n", + "state2_xmin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", + "state2_xmax | FLOAT | 1 | 5.0 | 5.0 | 5.0 | 0.0 \n", + "state2_ymin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", + "state2_ymax | FLOAT | 1 | 2.0 | 2.0 | 2.0 | 0.0 \n", + "x_cells | INTEGER | 1 | 60 | 60 | 60.0 | 0.0 \n", + "y_cells | INTEGER | 1 | 60 | 60 | 60.0 | 0.0 \n", + "xmin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", + "ymin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", + "xmax | FLOAT | 1 | 10.0 | 10.0 | 10.0 | 0.0 \n", + "ymax | FLOAT | 1 | 10.0 | 10.0 | 10.0 | 0.0 \n", + "initial_timestep | FLOAT | 1 | 0.04 | 0.04 | 0.04 | 0.0 \n", + "timestep_rise | FLOAT | 1 | 1.5 | 1.5 | 1.5 | 0.0 \n", + "max_timestep | FLOAT | 1 | 0.04 | 0.04 | 0.04 | 0.0 \n", + "end_step | INTEGER | 1 | 90 | 90 | 90.0 | 0.0 \n", + "test_problem | INTEGER | 1 | 2 | 2 | 2.0 | 0.0 \n", + "visit_frequency | INTEGER | 1 | 10 | 10 | 10.0 | 0.0 \n", + "\n", + "Table: output\n", + "\n", + "column | type | unique | min | max | avg | std_dev \n", + "-------------------------------------------------------------------------------------------------------------------------------------------\n", + "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792 \n", + "step | INTEGER | 90 | 1 | 90 | 45.5 | 25.979158313283875 \n", + "time | FLOAT | 90 | 0.0 | 3.56 | 1.7799999999999996 | 1.0391663325313554 \n", + "control | VARCHAR | 1 | None | None | None | None \n", + "timestep | FLOAT | 1 | 0.04 | 0.04 | 0.0399999999999994 | 6.036837696399289e-16\n", + "x | FLOAT | 1 | 0.0833 | 0.0833 | 0.08330000000000087 | 8.743006318923108e-16\n", + "y | FLOAT | 1 | 0.0833 | 0.0833 | 0.08330000000000087 | 8.743006318923108e-16\n", + "wall_clock | FLOAT | 711 | 0.00021386146545410156 | 0.1322460174560547 | 0.054996451073222694 | 0.03471222483372871 \n", + "average_time_per_cell | FLOAT | 706 | 5.9405962626139325e-08 | 3.724901212586297e-07 | 3.012494375437398e-07 | 8.578276515102002e-08\n", + "step_time_per_cell | FLOAT | 211 | 5.7220458984375e-08 | 3.2230880525377062e-06 | 3.3594816737109483e-07 | 8.433991864636084e-07\n", + "\n", + "Table: simulation\n", + "\n", + "column | type | unique | min | max | avg | std_dev \n", + "-----------------------------------------------------------------------\n", + "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792\n", + "sim_datetime | VARCHAR | 2 | None | None | None | None \n", + "\n", + "Table: viz_files\n", + "\n", + "column | type | unique | min | max | avg | std_dev \n", + "-------------------------------------------------------------------------\n", + "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792\n", + "image_filepath | VARCHAR | 80 | None | None | None | None \n", + "\n" + ] + } + ], "source": [ "# Let's get more details about the data\n", "store.summary()" @@ -554,7 +711,7 @@ ], "metadata": { "kernelspec": { - "display_name": "mydsi", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -568,7 +725,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.10.11" } }, "nbformat": 4, From ded0969fcf812633d93470730f36e31cc64a3393 Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Wed, 29 Apr 2026 09:26:03 -0600 Subject: [PATCH 095/109] updated diana tutorial with new dc --- examples/clover3d/schema2.json | 2 +- examples/dsi_diana_tutorial.ipynb | 1040 ++++++++++++++++++++++++++++- 2 files changed, 1011 insertions(+), 31 deletions(-) diff --git a/examples/clover3d/schema2.json b/examples/clover3d/schema2.json index fef0df32..2342f42e 100644 --- a/examples/clover3d/schema2.json +++ b/examples/clover3d/schema2.json @@ -1,5 +1,5 @@ { - "genesis_datacard": { + "dublin_core_datacard": { "primary_key": "Title" }, "simulation": { diff --git a/examples/dsi_diana_tutorial.ipynb b/examples/dsi_diana_tutorial.ipynb index 4d6da509..03fe9063 100644 --- a/examples/dsi_diana_tutorial.ipynb +++ b/examples/dsi_diana_tutorial.ipynb @@ -118,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "ffd67bca-2b0a-4eb0-bf56-12d09a2baee3", "metadata": {}, "outputs": [ @@ -127,7 +127,7 @@ "output_type": "stream", "text": [ "Created an instance of DSI with the Sqlite backend: dsi-diana-tutorial.db\n", - "Loaded clover3d/clover3d_dublin_core.xml into the tables: dublin_core_datacard\n", + "Loaded clover3d/dublin_core_dc.xml into the tables: dublin_core_datacard\n", "Loaded ./clover3d/ into the tables: input, output, simulation, viz_files\n" ] } @@ -308,10 +308,49 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "3e00fca3-3f72-4a4e-bd4c-7c61ec3ed113", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Table: viz_files\n", + "\n", + "sim_id | image_filepath \n", + "-------------------------------------------\n", + "1 | run_1/clover.00000.00001.00000.vtk\n", + "1 | run_1/clover.00000.00001.00010.vtk\n", + "1 | run_1/clover.00000.00001.00020.vtk\n", + "1 | run_1/clover.00000.00001.00030.vtk\n", + "1 | run_1/clover.00000.00001.00040.vtk\n", + "1 | run_1/clover.00000.00001.00050.vtk\n", + "1 | run_1/clover.00000.00001.00060.vtk\n", + "1 | run_1/clover.00000.00001.00070.vtk\n", + "1 | run_1/clover.00000.00001.00080.vtk\n", + "1 | run_1/clover.00000.00001.00090.vtk\n", + "2 | run_2/clover.00000.00001.00000.vtk\n", + "2 | run_2/clover.00000.00001.00010.vtk\n", + "2 | run_2/clover.00000.00001.00020.vtk\n", + "2 | run_2/clover.00000.00001.00030.vtk\n", + "2 | run_2/clover.00000.00001.00040.vtk\n", + "2 | run_2/clover.00000.00001.00050.vtk\n", + "2 | run_2/clover.00000.00001.00060.vtk\n", + "2 | run_2/clover.00000.00001.00070.vtk\n", + "2 | run_2/clover.00000.00001.00080.vtk\n", + "2 | run_2/clover.00000.00001.00090.vtk\n", + "3 | run_3/clover.00000.00001.00000.vtk\n", + "3 | run_3/clover.00000.00001.00010.vtk\n", + "3 | run_3/clover.00000.00001.00020.vtk\n", + "3 | run_3/clover.00000.00001.00030.vtk\n", + "3 | run_3/clover.00000.00001.00040.vtk\n", + " ... showing 25 of 80 rows\n", + "\n" + ] + } + ], "source": [ "# Preview the contents of the visualization files\n", "store.display(\"viz_files\")" @@ -329,10 +368,50 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "65ed9155-e447-43ac-b556-72b24e1207ee", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finding all rows where 'wall_clock > 0.10' in the active backend\n", + "\n", + "Table: output\n", + "\n", + "sim_id | step | time | control | timestep | x | y | wall_clock | average_time_per_cell | step_time_per_cell \n", + "-----------------------------------------------------------------------------------------------------------------------------------\n", + "1 | 80 | 3.16 | sound | 0.04 | 0.0833 | 0.0833 | 0.10525012016296387 | 3.6545180612140234e-07 | 3.0330816904703774e-06\n", + "1 | 81 | 3.2 | sound | 0.04 | 0.0833 | 0.0833 | 0.10552811622619629 | 3.618934026961464e-07 | 7.503562503390842e-08 \n", + "1 | 82 | 3.24 | sound | 0.04 | 0.0833 | 0.0833 | 0.10580706596374512 | 3.584250202023886e-07 | 7.556544409857855e-08 \n", + "1 | 83 | 3.28 | sound | 0.04 | 0.0833 | 0.0833 | 0.10608291625976562 | 3.5502984022679257e-07 | 7.410844167073568e-08 \n", + "1 | 84 | 3.32 | sound | 0.04 | 0.0833 | 0.0833 | 0.10635900497436523 | 3.5171628629089033e-07 | 7.443957858615452e-08 \n", + "1 | 85 | 3.36 | sound | 0.04 | 0.0833 | 0.0833 | 0.10663509368896484 | 3.4848069832995046e-07 | 7.503562503390842e-08 \n", + "1 | 86 | 3.4 | sound | 0.04 | 0.0833 | 0.0833 | 0.10691094398498535 | 3.453195865148106e-07 | 7.470448811848959e-08 \n", + "1 | 87 | 3.44 | sound | 0.04 | 0.0833 | 0.0833 | 0.10718393325805664 | 3.422220091253405e-07 | 7.417466905381945e-08 \n", + "1 | 88 | 3.48 | sound | 0.04 | 0.0833 | 0.0833 | 0.10745906829833984 | 3.3920160447708285e-07 | 7.470448811848959e-08 \n", + "1 | 89 | 3.52 | sound | 0.04 | 0.0833 | 0.0833 | 0.10774111747741699 | 3.362706537996785e-07 | 7.66913096110026e-08 \n", + "1 | 90 | 3.56 | sound | 0.04 | 0.0833 | 0.0833 | 0.13019490242004395 | nan | nan \n", + "2 | 80 | 3.16 | sound | 0.04 | 0.0833 | 0.0833 | 0.10534191131591797 | 3.6577052540249293e-07 | 3.0422210693359374e-06\n", + "2 | 81 | 3.2 | sound | 0.04 | 0.0833 | 0.0833 | 0.1056208610534668 | 3.6221145765935115e-07 | 7.549921671549479e-08 \n", + "2 | 82 | 3.24 | sound | 0.04 | 0.0833 | 0.0833 | 0.10589790344238281 | 3.5873273523842416e-07 | 7.523430718315972e-08 \n", + "2 | 83 | 3.28 | sound | 0.04 | 0.0833 | 0.0833 | 0.10617494583129883 | 3.55337837454146e-07 | 7.496939765082466e-08 \n", + "2 | 84 | 3.32 | sound | 0.04 | 0.0833 | 0.0833 | 0.10645294189453125 | 3.520269242544023e-07 | 7.556544409857855e-08 \n", + "2 | 85 | 3.36 | sound | 0.04 | 0.0833 | 0.0833 | 0.10673403739929199 | 3.488040437885359e-07 | 7.642640007866753e-08 \n", + "2 | 86 | 3.4 | sound | 0.04 | 0.0833 | 0.0833 | 0.10701203346252441 | 3.4564610291513055e-07 | 7.556544409857855e-08 \n", + "2 | 87 | 3.44 | sound | 0.04 | 0.0833 | 0.0833 | 0.10729098320007324 | 3.425638033207958e-07 | 7.556544409857855e-08 \n", + "2 | 88 | 3.48 | sound | 0.04 | 0.0833 | 0.0833 | 0.10756993293762207 | 3.3955155598996867e-07 | 7.556544409857855e-08 \n", + "2 | 89 | 3.52 | sound | 0.04 | 0.0833 | 0.0833 | 0.1078500747680664 | 3.3661072024989517e-07 | 7.556544409857855e-08 \n", + "2 | 90 | 3.56 | sound | 0.04 | 0.0833 | 0.0833 | 0.13074803352355957 | nan | nan \n", + "3 | 80 | 3.16 | sound | 0.04 | 0.0833 | 0.0833 | 0.10527896881103516 | 3.655519750383165e-07 | 3.060566054450141e-06 \n", + "3 | 81 | 3.2 | sound | 0.04 | 0.0833 | 0.0833 | 0.10556888580322266 | 3.6203321606043436e-07 | 7.801585727267796e-08 \n", + "3 | 82 | 3.24 | sound | 0.04 | 0.0833 | 0.0833 | 0.1058499813079834 | 3.585703973847676e-07 | 7.60952631632487e-08 \n", + " ... showing 25 of 88 rows\n", + "\n" + ] + } + ], "source": [ "# Search string or value within all tables\n", "store.find(\"wall_clock > 0.10\")" @@ -340,10 +419,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "266811c7-f79e-4bd4-9ee0-0e40a97fa8ed", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finding all rows where 'state2_density==8.0' in the active backend\n" + ] + } + ], "source": [ "# Perform a find and receive a collection\n", "find_list = store.find(\"state2_density==8.0\", True) # Use True to return a collection" @@ -351,10 +438,105 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "bee1a1b9-a749-4bde-ab1d-abebd386622f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_idstate1_densitystate1_energystate2_densitystate2_energystate2_geometrystate2_xminstate2_xmaxstate2_yminstate2_ymax...xminyminxmaxymaxinitial_timesteptimestep_risemax_timestepend_steptest_problemvisit_frequency
070.21.08.02.5rectangle0.05.00.02.0...0.00.010.010.00.041.50.0490210
\n", + "

1 rows × 22 columns

\n", + "
" + ], + "text/plain": [ + " sim_id state1_density state1_energy state2_density state2_energy \\\n", + "0 7 0.2 1.0 8.0 2.5 \n", + "\n", + " state2_geometry state2_xmin state2_xmax state2_ymin state2_ymax ... \\\n", + "0 rectangle 0.0 5.0 0.0 2.0 ... \n", + "\n", + " xmin ymin xmax ymax initial_timestep timestep_rise max_timestep \\\n", + "0 0.0 0.0 10.0 10.0 0.04 1.5 0.04 \n", + "\n", + " end_step test_problem visit_frequency \n", + "0 90 2 10 \n", + "\n", + "[1 rows x 22 columns]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Simply display what this collection (pandas dataframe) looks like\n", "find_list" @@ -362,40 +544,682 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "34ab0de4-00bc-4184-b4b5-6e3b4352367f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finding all rows where 'time>3.0' in the active backend\n" + ] + } + ], "source": [ "find_list = store.find(\"time>3.0\", True)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "d108854d-6f28-4adf-bcae-3de04c408fc7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_idsteptimecontroltimestepxywall_clockaverage_time_per_cellstep_time_per_cell
01773.04sound0.040.08330.08330.0937793.383084e-077.417467e-08
11783.08sound0.040.08330.08330.0940513.349399e-077.390976e-08
21793.12sound0.040.08330.08330.0943243.316600e-077.417467e-08
31803.16sound0.040.08330.08330.1052503.654518e-073.033082e-06
41813.20sound0.040.08330.08330.1055283.618934e-077.503563e-08
.................................
1078863.40sound0.040.08330.08330.1078073.482136e-077.530053e-08
1088873.44sound0.040.08330.08330.1080863.451018e-077.556544e-08
1098883.48sound0.040.08330.08330.1083643.420577e-077.523431e-08
1108893.52sound0.040.08330.08330.1086433.390857e-077.556544e-08
1118903.56sound0.040.08330.08330.130823NaNNaN
\n", + "

112 rows × 10 columns

\n", + "
" + ], + "text/plain": [ + " sim_id step time control timestep x y wall_clock \\\n", + "0 1 77 3.04 sound 0.04 0.0833 0.0833 0.093779 \n", + "1 1 78 3.08 sound 0.04 0.0833 0.0833 0.094051 \n", + "2 1 79 3.12 sound 0.04 0.0833 0.0833 0.094324 \n", + "3 1 80 3.16 sound 0.04 0.0833 0.0833 0.105250 \n", + "4 1 81 3.20 sound 0.04 0.0833 0.0833 0.105528 \n", + ".. ... ... ... ... ... ... ... ... \n", + "107 8 86 3.40 sound 0.04 0.0833 0.0833 0.107807 \n", + "108 8 87 3.44 sound 0.04 0.0833 0.0833 0.108086 \n", + "109 8 88 3.48 sound 0.04 0.0833 0.0833 0.108364 \n", + "110 8 89 3.52 sound 0.04 0.0833 0.0833 0.108643 \n", + "111 8 90 3.56 sound 0.04 0.0833 0.0833 0.130823 \n", + "\n", + " average_time_per_cell step_time_per_cell \n", + "0 3.383084e-07 7.417467e-08 \n", + "1 3.349399e-07 7.390976e-08 \n", + "2 3.316600e-07 7.417467e-08 \n", + "3 3.654518e-07 3.033082e-06 \n", + "4 3.618934e-07 7.503563e-08 \n", + ".. ... ... \n", + "107 3.482136e-07 7.530053e-08 \n", + "108 3.451018e-07 7.556544e-08 \n", + "109 3.420577e-07 7.523431e-08 \n", + "110 3.390857e-07 7.556544e-08 \n", + "111 NaN NaN \n", + "\n", + "[112 rows x 10 columns]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "find_list" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "38fb33ad-a6d8-489c-8195-f2d8f4cad785", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finding all rows where 'time(1.0,1.1)' in the active backend\n" + ] + } + ], "source": [ "find_list = store.find(\"time(1.0,1.1)\", True)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "5c167011-fd2c-4b1a-bf62-9e16b4017d20", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_idsteptimecontroltimestepxywall_clockaverage_time_per_cellstep_time_per_cell
01261.00sound0.040.08330.08330.0272032.906312e-076.947252e-08
11271.04sound0.040.08330.08330.0274602.825113e-076.973743e-08
21281.08sound0.040.08330.08330.0277372.751677e-077.496940e-08
32261.00sound0.040.08330.08330.0272342.909624e-076.781684e-08
42271.04sound0.040.08330.08330.0274842.827566e-076.748570e-08
52281.08sound0.040.08330.08330.0277362.751582e-076.801552e-08
63261.00sound0.040.08330.08330.0272602.912400e-076.755193e-08
73271.04sound0.040.08330.08330.0275042.829626e-076.609493e-08
83281.08sound0.040.08330.08330.0277482.752765e-076.583002e-08
94261.00sound0.040.08330.08330.0271562.901269e-076.688966e-08
104271.04sound0.040.08330.08330.0274052.819447e-076.722079e-08
114281.08sound0.040.08330.08330.0276612.744155e-076.920762e-08
125261.00sound0.040.08330.08330.0270562.890596e-076.662475e-08
135271.04sound0.040.08330.08330.0273012.808752e-076.609493e-08
145281.08sound0.040.08330.08330.0275472.732849e-076.642607e-08
156261.00sound0.040.08330.08330.0270542.890367e-076.609493e-08
166271.04sound0.040.08330.08330.0273042.809047e-076.748570e-08
176281.08sound0.040.08330.08330.0275552.733630e-076.808175e-08
187261.00sound0.040.08330.08330.0278282.973075e-076.834666e-08
197271.04sound0.040.08330.08330.0280772.888593e-076.755193e-08
207281.08sound0.040.08330.08330.0283292.810430e-076.808175e-08
218261.00sound0.040.08330.08330.0270102.885680e-076.609493e-08
228271.04sound0.040.08330.08330.0272562.804116e-076.669097e-08
238281.08sound0.040.08330.08330.0275132.729467e-076.947252e-08
\n", + "
" + ], + "text/plain": [ + " sim_id step time control timestep x y wall_clock \\\n", + "0 1 26 1.00 sound 0.04 0.0833 0.0833 0.027203 \n", + "1 1 27 1.04 sound 0.04 0.0833 0.0833 0.027460 \n", + "2 1 28 1.08 sound 0.04 0.0833 0.0833 0.027737 \n", + "3 2 26 1.00 sound 0.04 0.0833 0.0833 0.027234 \n", + "4 2 27 1.04 sound 0.04 0.0833 0.0833 0.027484 \n", + "5 2 28 1.08 sound 0.04 0.0833 0.0833 0.027736 \n", + "6 3 26 1.00 sound 0.04 0.0833 0.0833 0.027260 \n", + "7 3 27 1.04 sound 0.04 0.0833 0.0833 0.027504 \n", + "8 3 28 1.08 sound 0.04 0.0833 0.0833 0.027748 \n", + "9 4 26 1.00 sound 0.04 0.0833 0.0833 0.027156 \n", + "10 4 27 1.04 sound 0.04 0.0833 0.0833 0.027405 \n", + "11 4 28 1.08 sound 0.04 0.0833 0.0833 0.027661 \n", + "12 5 26 1.00 sound 0.04 0.0833 0.0833 0.027056 \n", + "13 5 27 1.04 sound 0.04 0.0833 0.0833 0.027301 \n", + "14 5 28 1.08 sound 0.04 0.0833 0.0833 0.027547 \n", + "15 6 26 1.00 sound 0.04 0.0833 0.0833 0.027054 \n", + "16 6 27 1.04 sound 0.04 0.0833 0.0833 0.027304 \n", + "17 6 28 1.08 sound 0.04 0.0833 0.0833 0.027555 \n", + "18 7 26 1.00 sound 0.04 0.0833 0.0833 0.027828 \n", + "19 7 27 1.04 sound 0.04 0.0833 0.0833 0.028077 \n", + "20 7 28 1.08 sound 0.04 0.0833 0.0833 0.028329 \n", + "21 8 26 1.00 sound 0.04 0.0833 0.0833 0.027010 \n", + "22 8 27 1.04 sound 0.04 0.0833 0.0833 0.027256 \n", + "23 8 28 1.08 sound 0.04 0.0833 0.0833 0.027513 \n", + "\n", + " average_time_per_cell step_time_per_cell \n", + "0 2.906312e-07 6.947252e-08 \n", + "1 2.825113e-07 6.973743e-08 \n", + "2 2.751677e-07 7.496940e-08 \n", + "3 2.909624e-07 6.781684e-08 \n", + "4 2.827566e-07 6.748570e-08 \n", + "5 2.751582e-07 6.801552e-08 \n", + "6 2.912400e-07 6.755193e-08 \n", + "7 2.829626e-07 6.609493e-08 \n", + "8 2.752765e-07 6.583002e-08 \n", + "9 2.901269e-07 6.688966e-08 \n", + "10 2.819447e-07 6.722079e-08 \n", + "11 2.744155e-07 6.920762e-08 \n", + "12 2.890596e-07 6.662475e-08 \n", + "13 2.808752e-07 6.609493e-08 \n", + "14 2.732849e-07 6.642607e-08 \n", + "15 2.890367e-07 6.609493e-08 \n", + "16 2.809047e-07 6.748570e-08 \n", + "17 2.733630e-07 6.808175e-08 \n", + "18 2.973075e-07 6.834666e-08 \n", + "19 2.888593e-07 6.755193e-08 \n", + "20 2.810430e-07 6.808175e-08 \n", + "21 2.885680e-07 6.609493e-08 \n", + "22 2.804116e-07 6.669097e-08 \n", + "23 2.729467e-07 6.947252e-08 " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "find_list" ] @@ -412,10 +1236,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "8fd1e99e-d293-46d2-b6e8-b353cf027f07", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Printing the result of the query: SELECT sim_id, xmin, ymin, xmax, ymax, state2_density FROM input\n", + "\n", + "sim_id | xmin | ymin | xmax | ymax | state2_density\n", + "---------------------------------------------------\n", + "1.0 | 0.0 | 0.0 | 10.0 | 10.0 | 2.0 \n", + "2.0 | 0.0 | 0.0 | 10.0 | 10.0 | 3.0 \n", + "3.0 | 0.0 | 0.0 | 10.0 | 10.0 | 4.0 \n", + "4.0 | 0.0 | 0.0 | 10.0 | 10.0 | 5.0 \n", + "5.0 | 0.0 | 0.0 | 10.0 | 10.0 | 6.0 \n", + "6.0 | 0.0 | 0.0 | 10.0 | 10.0 | 7.0 \n", + "7.0 | 0.0 | 0.0 | 10.0 | 10.0 | 8.0 \n", + "8.0 | 0.0 | 0.0 | 10.0 | 10.0 | 9.0 \n", + "\n" + ] + } + ], "source": [ "# Use sql statement to directly query the backend store\n", "store.query(\"SELECT sim_id, xmin, ymin, xmax, ymax, state2_density FROM input\") # Adding 'True' gives a collection" @@ -423,20 +1267,57 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "3f3d15ae-7c92-46b7-ac29-f09b495a7a75", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Table: dublin_core_datacard\n", + " - num of columns: 15\n", + " - num of rows: 1\n", + "\n", + "Table: input\n", + " - num of columns: 22\n", + " - num of rows: 8\n", + "\n", + "Table: output\n", + " - num of columns: 10\n", + " - num of rows: 720\n", + "\n", + "Table: simulation\n", + " - num of columns: 2\n", + " - num of rows: 8\n", + "\n", + "Table: viz_files\n", + " - num of columns: 2\n", + " - num of rows: 80\n", + "\n", + "\n" + ] + } + ], "source": [ "store.list()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "863d168c-b73a-424e-bf58-d13980a45da1", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: 'genesis_datacard' does not exist in this database\n" + ] + } + ], "source": [ "# alternative to \"query()\" if you want to get a whole table\n", "store.get_table(\"genesis_datacard\", True) # Adding 'True' gives a collection" @@ -515,7 +1396,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "d790e4e0", "metadata": {}, "outputs": [], @@ -525,10 +1406,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "18d62564-30f0-45af-b664-30f1862b32d9", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully wrote to the output file clover_er_diagram_no_schema.png\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApkAABanCAYAAABeTu5SAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd1gU1+I+8JcqIKCIEKzYe0UUxaBYsERjSaJXotEYE4NR1JjEdmO8V78paiwxBnsSEWMSvSYaYwzGktiwoRjFFguCNEVEitQ9vz982J/LzBaW2V3K+3mefR7nzDkzZ2d22dcpZ6yEEAJERERERMr5ytrSPSAiIiKiyochk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHEMmURERESkOIZMIiIiIlIcQyYRERERKY4hk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHEMmURERESkOIZMIiIiIlIcQyYRERERKY4hk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHEMmURERESkOIZMIiIiIlIcQyYRERERKY4hk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHG2lu4AVVyhoaH4+++/Ld0NIiIykQULFqBfv36W7gZVUAyZZLTo6GicOHHC0t0gIiITmTx5sqW7QBUYT5cTERERkeIYMomIiIhIcTxdTorp2bMnbGxsLN0NIiIy0o0bN5CUlGTpblAlwZBJirG2tsZff/1l6W4QEZGRWrRowZBJiuHpciIiIiJSHEMmERERESmOp8vJZDp06IDnn3/e0t0gIiIt1q1bB5VKZeluUCXFkEkm06dPH6xatcrS3SAiIi02btzIkEkmw9PlRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBRna+kOEFHFcu/ePRw8eBB3795FmzZtEBQUBBcXF0t3q9wqKCjArVu3cP36dVy/fh02NjaoX78+6tevjy5dusDOzk6jPrcvEVUWDJlEZLBvv/0WoaGhyMrKUpc1atQI27Ztg7+/vwV7Vr4IIbBlyxYsWbIEN27cQFFRkWy9+vXrY8aM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IiKAIZOoypM7Innp0iX0798fL774otbxF62srAx+2osxT4Up65NkSt4hXlpyRwUNuZTAkkq7zfbv3y8J3o0bN1ayS0RUhTFkElVxc+fOhZubm+y8vXv3okuXLhg6dCiioqKMXoeuIY+UbFPVlXabHTlyRFLGkElESmHIJKri3N3d8ddff6F+/fpa6/z666/o0aMHgoKCcI0Dse8AACAASURBVOHCBTP2jkzp9OnTkjJvb28L9ISIKiOGTCJCu3btEBUVhd69e+us98cff8DX1xfTp09HRkaGmXpHppKamiopy87OtkBPiKgyYsgkIgBAvXr1cOTIEezcuVPnKdOioiJ8+eWX8PPzw71798zYQ1JSQUGB7H8U7ty5Y/7OEFGlxJBJRBpefvllXLlyBZ999pnOu4avXbuG3r17IzEx0Yy9I6VYW1vD3t5eUh4XF2eB3hBRZcSQSUQS1apVw5w5c3Dz5k28++67smEEAG7evIk5c+aYuXekBBsbGzRr1kxSziOZRKQUhkwi0srd3R0rVqzAtWvX0K9fP9k627Ztw99//23mnpES5B5FqUTIFEKUeRlEVPExZBJVYbm5uahbty4ePXqks16jRo0QGRmJTz75RDJMjhACf/75pym7SSYiFzL/+usv3L17t0zLLc/PeCci82HIJKrChBBISkrC7t279da1trbGvHnzMHfuXMm8K1eumKJ7ZGJyITM/Px8ff/xxmZZb3getJyLzYMgkIoMfDwkACxcuRJ06dTTKatasqXSXyAz69+8PZ2dnSfk333xTphuACgsLS/WceCKqnBgyiQgHDhxAUlKSQXXt7Ozg6+urUdatWzedbUoe2TLkSJdcSClNcJE7ZVuaZ5fLtS/vR+hKu53r1KmDDz/8UHY58+bNK9O1lRxHlYgYMokI+fn5GDdunMHX0pU8ctm1a1ed9bOysjSmDRnwu2QbQ9sVkwtYT548Mbi9XKAt7wOVG7Od3333Xdm7zLdv347g4GDk5ubqbP/jjz/izJkzkvKyPIaUiCoHhkwiAgAcOnQIixYt0ltPCKERIIKDg1G3bl2t9VNTU5GXlycp03eULT4+XlKWnJyst3/FHj58KCl78OCBwe3lboYqz4PPG7ud7e3tsXLlStl5P/zwA/r374+UlBTJvKKiInzwwQf417/+JRtmFy1ahHv37uHhw4fYu3cv8vPz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586dUy+99JLy9PQ0tPP19VUhISGqadOmVvsfPny4ysnJsdj3wYMHVfPmze1a/5ZezZs3V0qV/rNnya5du1SHDh1M+mjZsqUaPny42fpbtmyptmzZYrPv119/XbVq1cpmvS1atFBKKZWdna1ef/115erqarPNxIkT7X6PjjZ69Gij2sPDw42GIyIiHF0iqq7PCJlwiMoQMvPz81W7du2Mag4KClKxsbGqsLBQKaVUZmam+sc//qFq1KhhNF+dOnUs9rtx48ZS/dC/8sorNtvn5uaqiRMnFiuQeHp6quPHjyul/gqDbdq0KVb77t27271ur1y5YjOE3R4yi1y7ds1m+CiPkLlz507l7+9vmN/Hx0ctX75c5efnG+Y5evSo6tevn8Vl9O3bV2VmZprtv2HDhsVa/+ZeRSGzSGk/e7das2aNyXdaRNS//vUvw/dDKaU2b95s8oeOh4eHWrRokc1l5OXlqYEDB1qtt0WLFiozM1P16dOnWOtmwYIFdr1PRyNkwokRMuEYlSFkzpkzx+Q9rFq1yuy8iYmJqm7dukbv1ZqWLVuW+If+999/t/kD+txzzykRUdWrV1eDBw9WX3zxhfroo49UUFCQ1XbDhw9XqamphhDn6+urpk6dqr799ls1duxYVb16davtf/75Z7vX7+uvv16ikKmUUjNmzHBoyNy8ebPR3ksRUf/85z/NzpuZmanatm1rcTmTJ0822+73339XCQkJKiEhQb322mtWax01apRh3ltfiYmJJv2W5rNX5JdffjH7Wejfv7/Z+RcsWGB2WW+88YbNZX333XdW623cuLHq3r27EhFVu3Zt9cILL6h33nnH7B7WW1/+/v5W9yQ7C0ImnBghE45R0UNmRkaGql27tsl7iI+Pt9hmy5YtRj+82dnZFue1tXfG2g/9n3/+aTNkioiqX7++Sb2HDx+2GhRdXFxUcHCwEhHVp08fdenSJaP2//jHP6wuMzIy0s41rNS8efNKHDK//PJLh4XMrKws1axZM5P5H3vsMYvLevPNNy0up1q1aiopKclqrZ988onVWqdMmWLz/RYpzWdPKaXS0tKUn5+f2baxsbFm22RlZSlfX1+zn7fbT9O43fbt2+36vHfr1s3olI3c3FyTIxG3v77//nu715ujEDLhxD7jFkZACWzfvl3+/PNPk/HffPONxTb33nuvzJ492zB8+0U2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c+dOl+uIj4/X/PnzNW/ePJ08edJp3+joaD322GM6depUjn2ysrK0YMECZWZm6ssvv8xuT0tL06hRozR9+vRcv0e/fv20bNky9e7d2+XvUVTyO8/OfPPNNxoyZEj2v+Nu3bpp2LBhatmypQIDA3X27Fl99tlnmjhxonbt2qXmzZvL39/flH0DKFkImYCbMwxDTz/9tBISEvS3v/1NW7ZsUVBQkE2fatWqacyYMXryySfVpk0bxcbG5ri9EydOqFGjRrp69Wqe6li5cqUeeeQReXh4KD093Wnfn376SQ899JCuXbum5s2bq1mzZvLz89PWrVu1detWpaam2vRfuHChOnbsqCeffFIZGRnq37+/li9fLkkqW7asWrVqpZCQEG3btk2HDx+2GZuZmamhQ4eqa9eu8vX1zdN3Kkz5nWdnFi1apH/84x8yDEMeHh4aP3683n77bZu7xytVqqQJEybo3nvv1cMPP6z09HRdvnzZtBoAlBzc+AO4uVmzZmnTpk2SpNGjR9sFzJtVqVJFixYtyj7C6UhYWJhWrFihpKQkJSQkqFu3bi7V8eCDD2rz5s26cuWKYmNjdeeddzrst379enXv3l1NmzbVrl27FB0drRkzZmjKlCnatGmTfvnlF4WGhtqNmzt3rrKysvT0009r+fLl6tmzp7Zu3aqLFy/qv//9rxYtWqQ//vhDq1evtnuDTVxcnD755BOXvkdRye885+Q///mPBg0aJMMwJEkjR47Uu+++m+Pjibp3766FCxfy+CIAObP4fH2h4ppMIHft2rXL/j2sW7fOpTGjRo0yJLl0TeYvv/xi97tz5VrBKVOm2I1r2rSpERwcbMybN8/IysrKcezu3bsdXmP58MMPG35+fsbixYud7nvZsmV2Y9u3b59rzVbK7zwbhmEkJiYad9xxR/a4cuXKGRcuXHBp7OOPP263X67JBLgm0zCMmfwvKODmfv/99+x/jomJcWnM4MGDXd5+3bp181yTJNWrV8+u7cCBA1q2bJmee+45eXh45Di2cePGatmypV37li1btHHjRj3xxBNO992rVy+7u8q3b9+urKwsF6svevmdZ0kaPny4zp8/n708ZswYl++q7969e773C6BkI2QCbiw+Pl4XL17MXp4+fXqu10NKUu3atdWsWTOX9hEcHCwfH58811axYkW7tkGDBql9+/Yuje/cubNd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+HG7try8ChMACgMhE4ClDMNQfHy8VqxYkWtfT09PjRs3TmPHjrVbl9NrG/PjgQcecHiqedy4cXZHDHOTkpKiyZMnq2fPnvL0dO1PblpaWp72ceLECZtlX19fdevWLU/bAACzETIBFAt5eT3km2++qcqVK9u0BQUF2fVzFOpceexRhQoV1L17d7v2ffv2qV+/fi4faczKytLAgQMVFxen3r17uzRGksPXTzpz62ss//GPfyg0NDRP2wAAsxEyARQLa9eudfmaRR8fHzVt2tSmLSIiwq6fo7urExMTHW4zJibGJty99tprDvt999136tatm06ePOm0xrS0NA0ZMkTfffed6tatq06dOjntf7Pctn2z8+fP67PPPste9vLy0quvvuryeAAoLIRMAMVCWlqaBg4cqKysLJf633rkslmzZrn2kf4Ms7datGiR2rVrZ3OKvG3btjm+SnL9+vWqX7++Ro8erT179tjcOX769GnNmzdP4eHhmjdvnry9vbVw4cIc7453ZOXKlS73/fDDD23C8fjx4/W3v/3N5fEAUFgImQCKjQ0bNujtt9/OtZ9hGIqOjs5e7t+/v6pUqWLXLzw8XL6+vjZtv//+u9577z1lZGTo0qVL+uKLLzR48GC9+uqr8vf3t+n7+eef684773RYw9WrVzV16lQ1btxYvr6+CgsLU0BAgKpVq6bBgwdnH40cN26cwwDszOeff+7wZp5bnT9/XrNmzcpebtGihd5888087QsACgshE0Cx8s4772jkyJFOr0ucMmWKDh8+LElq3Lixzenim5UuXVpdunSxax8/frwCAwNVoUIFDRo0SHXq1NFLL71k1698+fKKjIxUpUqVnNaclpamkydP2tXcsmVLvfHGG07HOnLlyhV16tTJ6duMjhw5ojZt2mQ/H7Nu3bpaunSp3Z33AGAVQiaAYiUrK0vTpk1TgwYNtGDBAsXExCgzM1OGYSg2NlbPPfecxo0bJ0mqXbu2fvjhB7sjkDebP3++6tSpY9d+48YNpaenq3bt2lq9erXKlCnjcHyjRo20ZcsW1atXL0/fo1OnTlqzZo18fHzyNE6S7r77bh05ckR169bVP//5Tx06dEhpaWnKyMjQrl27NGPGDEVERGTfUd+8eXNt3rxZVatWzfO+AKCwEDIBFAtVq1ZV69atVbZsWUl/PvvxmWeeUf369VW2bFkFBASodu3amj9/vgIDAzV16lTt378/x/d2/yUkJESrV69Ws2bNbK659Pb21tChQ7V169Zcn19Zs2ZN7d69W++9916ur2oMDg7WjBkz9J///EcBAQGufflbbNiwQS+++KJSUlL01ltvqW7duvL391fZsmXVpEkTDRs2TElJSQoJCdGsWbO0ZcsWVahQIV/7AoDC4m11AQDcW6lSpTR37lz16dNHISEhysrK0oEDB/Trr79q165dOnv2rM6dO6dy5cqpQYMGatCggTp37pynUFW9enVt27ZNiYmJ2rBhg4KCgtSkSZM8PeanVKlSGjt2rEaMGKE1a9Zo7dq1iouL07lz5xQcHKxq1aqpc+fO6tq1q9Mjq67w8/PTrFmzNHr0aEVHRysmJkYxMTE6e/asQkNDddddd6lr165q3759vo6UAkBRIGQCsJSXl5eef/757GVPT0/Vr19f9evXN31foaGh6tevX4G24evrq169euV457mZwsLCHL7iEgBuB5wuBwAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMALCQo9douvpqTQAozgiZAGChlJQUl9oA4HZDyAQAC129etWu7a9XRQLA7YyQCQAW+evB87fas2ePBdUAgLkImQBggevXr+u1117TuXPn7NYNHz5cR44csaAqADAPb/wBgCJy6tQpvfPOO9q5c6d+//13ZWRkOOx3+vRpNWjQQC1btlS1atU0YMAAdenSpYirBYCCIWQCQBHx8fFRjRo1VKNGDT3yyCMujwsMDCzEqgCgcBAyAaCIVKpUSePGjbO6DAAoElyTCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA07n9G38mTJigUqVKWV0GAAAoQbZt22azfPXqVYsqsY5bhcysrCy7tlmzZllQCQAAcCdnzpyxuoQix+lyAAAAmI6QCQAAANO51elyR0qXLm11CQAAoITJzMxURkZG9rI75g23CpmenvYHbs+ePaugoCALqgEAACXVpEmT9Prrr2cvh4WFKSEhwcKKih6nywEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmM7b6gIAID8iIyN18OBBNWnSRO3bt7e6HADALQiZAG47ffv21bJly7KXR4wYoWnTpllYEQDgVpwuB3BbWb9+vU3AlKSPP/5Yhw4dsqgiAIAjhEwAt5WjR4/mqR0AYA1CJoDbSosWLeTpafuny8/PT/fee69FFQEAHCFkAritNGjQQNOmTZOPj48kyd/fX/PmzVOlSpUsrgwAcDNCJgDLzJ49W+vXr8/zuGHDhun06dPasmWLTp06pQEDBhRCdQCAgiBkArDEtWvX9Prrrys2NjZf4ytUqKCWLVuqfPnyJlcGADADIROAJRYuXKjk5GSrywAAFBJCJoAil5aWpsmTJ1tdBgCgEBEyARS5yZMn68SJE1aXAQAoRIRMAEVq5cqVevPNN60uAwBQyAiZAIrMvn37NGDAABmGYXUpAIBCRsgEUCTOnTunnj176urVq1aXAgAoAt5WFwCg5Nu9e7f69OmjY8eO2a27fPmyEhIS7NoDAgJUpkyZHLd56dIlLVy4UAsXLlRoaKh+/PFHl+s5ceKE5s2bp+XLl6t///5644037PocPnxYq1ev1m+//aY9e/YoNDRU9erV06BBg9S4cWOn28/IyFBkZKRWrFiho0ePKikpSWFhYWrRooWGDBliymOX0tPTtXHjRq1cuVKHDh3S2bNnlZWVpYYNG6pRo0Zq1KiRWrdurXLlyhV4XwCQL0YJFhUVZUjK/lSsWNFmWZKRlJRkdZlAiZWUlGSMGTPG8PX1tfvt5fZ56623HG5z27ZtxjPPPGP4+/tn923YsGGutWRmZhqrVq0yunfvbnh6emaPHTp0qE3fuBaMAAAgAElEQVS/q1evGqNHjzZ8fHwc1uXp6WkMGjTISE1NdbifFStWGJUrV87xe5UpU8ZYunRp3ifzpu8xd+5cIzQ0NNc5rFy5shEZGZnvfQHIv4kTJ9r8HiMiImyWw8PDrS6xsM3kSCaAQvPdd9/pyy+/VFBQkCQpNTVVSUlJNn0CAwPl7+9vNzYgICD7n69du6avv/5ac+bM0c6dO/NUQ3x8vObPn6958+bp5MmTTvtGR0frscce06lTp3Lsk5WVpQULFigzM1NffvlldntaWppGjRql6dOnO93HtWvX1K9fPy1btky9e/fO03dJSEhQz549tW3bNnl5een555/XAw88oEaNGuncuXOKiorSnDlzdOXKFUl/fvcePXpo0KBB+vjjjxUYGJin/QFAgVgdcwsTRzKB4mXVqlV2v8FPP/3U6Zjjx48bgYGBhqenp80RyJs/OR3JXLFiheHt7Z3jUUnddCRzw4YNRpkyZQxJRvPmzY2XX37ZGDVqlNGmTRujdOnSDscuXLjQMAzDSE9PN/r06ZPdXrZsWaNr167GgAEDjPDwcIdjq1WrZty4ccPluTty5IhRs2bN7CMg0dHRDvvFxsYaFSpUsNtfp06dXN4XgILjSKYxkxt/ABRrYWFhWrFihZKSkpSQkKBu3bq5PPbBBx/U5s2bdeXKFcXGxurOO+902G/9+vXq3r27mjZtql27dik6OlozZszQlClTtGnTJv3yyy8KDQ21Gzd37lxlZWXp6aef1vLly9WzZ09t3bpVFy9e1H//+18tWrRIf/zxh1avXi0/Pz+bsXFxcfrkk09c+h43btxQz549dfToUQUHB2vTpk1q3ry5w741a9bU999/r9KlS9u0r127VosXL3ZpfwBgBkImgGLvwQcfVGBgoO644w6HN+nkJDAwUC1atFDp0qVVs2ZNDR061K7Pr7/+qr59+2r69On66aefdM8999j1adKkiaKiouzaf/75Zz366KNavny5Fi9erBUrVigiIkLe3rZXInXp0kVfffWV3fjIyEiXvseIESMUExMjSZo+fboqV67stH+rVq00bdo0u/aRI0faXa4AAIWFkAngtlK3bt18j61Xr55d24EDB7Rs2TI999xz8vDwyHFs48aN1bJlS7v2LVu2aOPGjXriiSec7rtXr152d5Vv375dWVlZTsfFxMRo7ty52fUPHDjQaf+/9OvXT56etn/iExMT9cEHH7g0HgAKipAJ4LYSHBwsHx+ffI2tWLGiXdugQYPUvn17l8Z37tzZru39999XRERErmO9vb3tTnFfuXJFly9fdjru5iOSbdu2dalO6c95cnR5QF5vnAKA/CJkArjt3Hq9oat8fX3t2pwdvbyVo+syMzIyXB4fEhJi1/bXneCO3LhxQ4sWLcpebtOmjcv7kqQqVarYtR04cCBP2wCA/OIRRgBuO/k9kpnfcX9xFDIzMzNdHu/oIezOQub27duVkpKSvfziiy9q+PDhLu/v0qVLdm0nT57UtWvXnD7oHgDMQMgEcNu59caawh73l1vvEM8rR0dg09PTc+y/ZcsWm+XatWub8gafxMRE1ahRo8DbAQBnCJkAbjteXl5FOs4q+/fvt1meMGGCevXqZVE1AJA3XJMJAMXUxYsXbZaPHj1qUSUAkHeETAAopm59piUhE8DthJAJAMXUjRs3bJYJmQBuJ4RMACimgoODbZZjY2MtqgQA8o6QCQDF1K2PPDp06JC2bt1aoG0ahlGg8QDgKkImABRTYWFhdm2jR4/O9/ZSUlJ07733Kj4+viBlAYBLCJkAUEy1bt3arm3z5s2KjIzM1/Y+++wzxcfHO3y9JgCYjZAJAMXUAw884PC1l+PGjcvT6yylP49iTp48WT179pSnJ3/6ARQ+/tIAKDKOwo2jVx/iTxUqVFD37t3t2vft26d+/fo5fVvQzbKysjRw4EDFxcWpd+/eZpcJAA4RMgEUmcDAQLu2xMREh31jYmJ07do1h+tuDVeuhi1HR//yckQwKyvLri0v7y53ND632l977TWH7d999526deumkydPOh2flpamIUOG6LvvvlPdunXVqVMnl+sFgIIgZAIoMkFBQXZta9eutWtbtGiR2rVr5/BUsSRdvXrVZjmnMJrbuLyMlRwHwlufZemMo0Cb2/7btm2b46sk169fr/r162v06NHas2ePzZ3jp0+f1rx58xQeHq558+bJ29tbCxcuVKlSpVyuFwAKgpAJoMiEh4fL19fXpu3333/Xe++9p4yMDF26dElffPGFBg8erFdffVX+/v5220hMTFRqaqpdmytHM0+dOmXXdvbsWZfrv/U1j5J0/vx5l8cnJyfbtZ0+fTrXcZ9//rnuvPNOh+uuXr2qqVOnqnHjxvL19VVYWJgCAgJUrVo1DR48OPtI57hx49SsWTOXawWAgiJkAigypUuXVpcuXezax48fr8DAQFWoUEGDBg1SnTp19NJLLzncxpIlS+zaUlNTtXz58lz3v3jxYru26Ohol4JeSkqK/vWvf9m1r1mzRtevX891/KlTpxzW+P333+f67Mry5csrMjJSlSpVctovLS1NJ0+etDs62rJlS73xxhu51ggAZiJkAihS8+fPV506dezab9y4ofT0dNWuXVurV69WmTJlstddu3ZNp06d0rx58zRq1CiH233mmWf01VdfKS4uzib0Xbp0SQcOHNCwYcP07bff2o27fPmyunTponXr1uns2bM2p7/T0tIUHx+vLVu2qH///tq7d6/d+N27d6tr165at26d4uPjbY6ypqWl6bffftMHH3ygHj166MqVK3bjly1bpkGDBmnXrl0Oj5T+pVGjRtqyZYvq1auXYx9HOnXqpDVr1sjHxydP4wCgwIwSLCoqypCU/alYsaLNsiQjKSnJ6jIBt3Ps2DGjWbNmhoeHR/Zv0dvb2xg6dKhx4cIFu/5Vq1a1++06+zRv3twwDMNISEjI0zhJRuPGjbP3O3r06DyP79+/f/b4Nm3a5Hn8iRMnnM5damqq8d577xmBgYFOtxMcHGzMmDHDSE9PN+nfGoC8mDhxos1vMiIiwmY5PDzc6hIL20zvAiVUAMiH6tWra9u2bUpMTNSGDRsUFBSkJk2aKDQ01GH/uLi4fO0nNDS0QK9RnDx5siZPnpzv8Zs2bcr32JyUKlVKY8eO1YgRI7RmzRqtXbtWcXFxOnfunIKDg1WtWjV17txZXbt2dXhNKwAUFUImAMuEhoaqX79+VpdxW/L19VWvXr1yvPMcAKzGNZkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmI6QCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpvqwsoSoZh2LW1atVKnp5kbQAAYJ5z587ZLCcmJlpUiXXcPmTGxMRYUAkAAHAnycnJVpdQ5DiEBwAAANMRMgEAAGA6tzpd7uHhYdd29913c00mAAAw1blz55SQkJC9HBQU5HanzN0+ZG7ZskVBQUEWVAMAAEqqSZMm6fXXX89eDg0N1fHjx60ryAIcwgMAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmI6QCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpvqwsAAJQMZ86c0ZIlS1S2bFkNHjzY6nIAWIwjmQCAfDMMQ+vWrdMjjzyisLAwvfbaa5o3b57VZQEoBjiSCQDIs4sXL+qLL77QnDlzdPjwYavLAVAMETIBAC67ePGiRo4cqW+++UYpKSlWlwOgGON0OQDAZeXKlVNwcLDmzJmjr776SrVq1bK6JADFFEcyAQAu8/Ly0rRp07KXPT09NWDAAAsrAlBccSQTAEw0e/ZsrV+/3uoyikzXrl2tLgFAMUXIBACTXLt2Ta+//rpiY2OtLqXIBAcHy8PDw+oyABRDhEwAMMnChQuVnJxsdRlFysPDQ6VLl7a6DADFECETAEyQlpamyZMnW12GJXx8fKwuAUAxRMgEABNMnjxZJ06csLoMS3h68p8SAPb4ywAABbRy5Uq9+eabVpdhGa7JBOAIIRMACmDfvn0aMGCADMOwuhTLEDIBOELIBIB8OnfunHr27KmrV69aXQoAFDs8jB2AnYMHD+qHH37Qjh07FB8fr0uXLiksLEzh4eEKDw9Xnz59VKlSpQLt48KFC/r3v/+tAwcO6OjRozp58qSCgoJUuXJlhYeH6+GHH1azZs3yvf39+/drzpw5WrNmjSZMmKB//OMfdn02b96sb775RjExMUpOTlaNGjXUrl07Pfvss/L393e6/d27d6tPnz46duyY3brLly8rISHBrj0gIEBlypRxuL0//vhDn376qX788Ue99tpreu6557LXpaamaubMmVq/fr0SExN177336o033tBdd92V2zRIKvy5BgCHjBIsKirKkJT9qVixos2yJCMpKcnqMoFiY8uWLUbz5s3tfie3fsqUKWO88cYbxuXLl/O8jz179hiDBg0yfH19c91P9erVjS+++MLlbaekpBiLFi0yWrdubbOd6dOn2/TbuXOnXZ+bP6GhocaOHTsc7iMpKckYM2aMS/Xf+nnrrbdstpWWlmZ88803Rrt27Wz6TZ06NbvP3r17jXr16tltKygoyIiNjbVsrm8WHBxss62mTZvmaztASTJx4kSb30VERITNcnh4uNUlFraZnC4HoPT0dA0ePFitWrXS1q1bFR4erilTpuinn37S2bNntXv3br311lsKCAiQ9OdDx9955x3VqVNHR44ccXk/c+fO1X333acFCxbIz89P48eP1969e3Xt2jX9/vvvmjFjhjp37pzd//jx43r66afVoUMHxcXF5bjdw4cPa9SoUapataoGDhyon3/+Oce+CxYsUMuW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pKamqjFjxjhU6+npqTZt2pSr+YyLi7P/0vf09FTPP/+8WrVqlTp69KjasWOHGj9+vCpTpoxun0OHDtWEJiNRUVH23zf16tVT33zzjcrIyLB//+LFi2r27Nm6wJk5ZJr1muf3sUdGRqqJEydmGVjv1MTExKj69es79NrWrFlTHTt2zJmXFChUCJmETEImiqyvvvpK9/P80UcfZVtz/vx55e/vrzw8PDRnIXMKmZs3b1ZeXl5ZnpW8O2R+9913qlSpUkpEVPPmzdWoUaPU+PHjVZs2bVSJEiUMa1etWqWUUio9PV317t3bPl6mTBnVqVMnNWDAABUSEmJYW6VKFXX79m2n5u6PP/5QNWrUsP+i/+mnnwzXi4yMVEFBQbp9dujQIctt22w29cgjjygRUQ888EC2v2NiYmJUzZo1swyZmeXmNc/MzGO32Wyqa9eu2Z6VvBMyr127Zt+vo19+fn4ETRRZhExCJiETRVZuA8fu3btVYmKiunLliurcubNDITMxMVH9+OOPKiUlRUVGRhqepRw9erTauXOn8vX1VY888oj69ddfddv55ZdfDM92Pfzww8pqtaoBAwYoEVHdu3dXBw8eVOnp6Zr6bdu2KV9fX139u+++6/C83bp1S9WrV0+JiAoMDFSxsbHZrr9v3z7DcLx69WrD9d9//337OkuXLs2xnwMHDihPT88CCZn5cew7duxQkZGRKjIyUg0fPjzLkNm3b18l8vfbA3r06KHmzp2rVq9erYYPH67uv//+LINmw4YNVUpKisPHCBQWhExCJiETRZYZZ7V+/PFHh0JmZm+99ZaurmnTpiowMFAtWbJE2Wy2LGuPHj1qGCZ69eqlfH191Zo1a7Ld9xdffKGrbdeuncPHPGzYMHvdJ5984lDNwoULdfsMDg5W169f16179/sRd+7c6dD2x48fXyAhM7+PPS0tzfB9tHe20bdvX8PL7bdu3bKHUKOvN954w+FjBAoLQqZawCOMADdWp06dXNXVrVtXN/bbb7/JF198If/85z+zvTP4wQcflJYtW+rG9+/fL3v27JGnn34623336NFDd1f54cOHxWaz5dh3RESELF682H4MAwcOzLFGROSpp54SDw/tr8vLly/LnDlzdOseO3ZMsz9HDBs2zKH18qIgjt3b29vwZ2rcuHEyZswYWb9+vfj7++u+7+vrK5999pkMHz7c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JoqwHj16GIYEX19f5e3trURENW7cWCUlJRnWz50717B+3bp12e73ySef1NX4+/urP//8M8eeb9++rRo2bKirb9y4sUpOTs6x/sKFC5ogcufriSeeUDabLcd6pZQ6duyYqlixYo5hy+irZcuWWYbwzCHTw8NDvfjii1nOv1JKzZ49277+gw8+mOMc5PU1z69jv8MoZIqIqlWrVrY/H2fPnlV169a1r1+nTh2Hfp6AwoqQScgkZKJIu3r1qqpdu3aWoaBWrVoqLi5OU5OUlKQuXLigFi9ebA8lmb/8/PzUqlWr1MWLF+2h58aNGyoiIkKNHj06y/3Vr19f7dixQ126dEndunXLvs/U1FQVGxur9u3bp3r27JllfevWrdWOHTtUbGysSklJ0dQfPnxYvf3226pRo0ZZ1g8ePFgdOXJEXbt2Lce5i4yM1IQaR746dOigbt68meU2M4fMO1/Vq1dXy5YtU6dOnVIZGRnKZrOpP/74Qz333HPKYrHYXytHzvbl5jUviGO/wyhk1qtXT4mIKl26tHrllVfU6dOnVWpqqkpPT1dHjhxR8+fPV4GBgfb1mzdvri5fvpzjvoDCjJBJyCRkosiLiopSzZo1s4cVEVFeXl5q9OjRhmHr3nvvdSpcNG/e3PCyek5fDz74oH2fEyZMcLr+7svGbdq0cbo+Ojo6x7lLTU1Vb7zxhvL39892W4GBger9999X6enp2W7vTsi899571cMPP2x4xtXX11f5+fnZl8uWLavefvttlZqamm+veUEc+x1GITMuLk6NGDFCeXl52cc8PT11l/7LlSunPvjgAy6Ro1ggZKoFFqXy8bPBXGzHjh3SsWNH+3KFChUkPj5es05CQkKWjwsBipLLly/Ld999JwEBARIaGsojX5yQkpIi3377rezYsUP+/PNPuXLligQGBkqVKlWkY8eO0qlTJ/Hz88txO1arVZYtWya9e/eWcuXKic1mk99++00OHDggv/76q8TFxcmVK1ekbNmy0qBBA2nQoIF07NhRgoKCctW3Ga+5Wcd+R9++feWLL77QjCUmJoq/v79ER0fLTz/9JBERERIRESFxcXESHBwsVatWlU6dOkm7du3E29vb6WMACqNZs2bJ1KlT7cthYWFy6NAh+3JISIj8/vvvrmitoHzg5eoOAJgjODhYnnrqKVe3USSVLFlSevTokeXd147y9PSU559/3r7s4eEh9evXl/r16+e1RUNmvOZmHbsjqlWrZvgRlwCKJx5hBAAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAADTGH2UpqMfrwmgeCFkAgBMk5KS4tAYgOKPkAkAME1SUpJu7ObNmy7oBICrETIBAKa48/D5zI4fP+6CbgC4GiETAJBnt27dknHjxsmVK1d03xs7dqz88ccfLugKgCvxiT8AgFy5ePGivP7663LkyBE5duyYZGRkGK4XExMjDRo0kJYtW0qVKlVkwIAB8vjjjxdwtwAKGiETAJAr3t7ecv/998v9998vffr0cbjO398/H7sCUFgQMgEAuVKxYkWZPHmyq9sAUEjxnkwAAACYjpAJAAAA0xEyAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJjO7T/x5+zZs1KmTBlXtwEAAIqRq1evapatVquLOnEdtwqZNptNNxYWFuaCTgAAgDuJjIx0dQsFjsvlAAAAMB0hEwAAAKYjZAIAAMB0bvWeTA8PfaY+dOgQN/4AAABTLV68WN577z37cs2aNeWXX35xYUcFz61CppGQkBAJCAhwdRsAAKAYKV++vGbZ09PTRZ24DpfLAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmI6QCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACm83J1AwBQ0NLT0+X69etO1QQHB4vFYhEREavVKlevXs12/RIlSkhAQECuewSAoo6QCcDtHD58WFq1auVUTVJSkpQqVUpERKKjo6VmzZrZrt+jRw/58ssvc90jABR1XC4H4HZatmwpERERMm/ePGnXrl22677++uty6dIle8AUEalRo4acO3dOnn32Wd36vXr1knXr1sknn3xiet8AUJQQMgG4pbp168qYMWNk165dMnbsWMN1PDw85KWXXpKKFSvqvnf//ffLyJEjNWNz5syRjRs3Sr9+/aRMmTL50jcAFBWETABub+7cudK/f3/duM1mk61bt2ZZt3r1avt/v/vuu/LSSy/lS38AUBQRMgFARD766CMJCQnRjb/00kuSlJSkGz969KgsWLBARESGDBkiL774Yr73CABFCSETAESkTJkysn79evHx8dGMX7hwQSZNmqQZS09PlyFDhkh6erqEhYXJokWLCrJVACgSCJkA8F+NGzeW119/XTe+cOFC+fHHH+3L06ZNk2PHjkm5cuXkiy++kBIlShRkmwBQJBAyAeAu48aNk9atW2vGlFLyr3/9S9LT02XXrl3y9ttvi8VikZUrV8p9993nok4BoHAjZALAXTw8PGTlypVSunRpzfjJkydlypQp8swzz4hSSl566SXp0qWLi7oEgMKPkAkAmdx///0yd+5c3ficOXPk0qVL0qJFC3njjTdc0BkAFB2ETAAw8Nxzz0m3bt10415eXvLpp5+KlxcfmAYA2SFkAkAWlixZIuXLl9eMZWRkyMaNG13UEQAUHYRMAMhChQoVZN68ebrxKVOmyMmTJ13QEQAUHYRMAMjG5s2bdWOpqanyzDPPSFpamgs6AoCigZAJAFlYuHChrF+/XoKCgqRatWqa7x09elRefvllF3UGAIUfIRMADBw5ckT+85//iMVikdWrV8uKFSvEYrFo1nn77bdl3759LuoQAAo3QiYAZJKYmChPPvmkpKamypQpU6Rjx47Stm1bGTNmjGY9m80mgwYNkps3b7qoUwAovAiZAJDJc889J5GRkdKmTRt59dVX7eNvvPGG1KlTR7NuVFSUjB07tqBbBIBCj5AJAHd5//33ZcOGDRIUFCRr164VT09P+/d8fX1l5cqVmjERkeXLl8uXX35Z0K0CQKFGyASA//r5559l3Lhx9vdhVq5cWbdOWFiYTJ48WTc+bNgwiY+PL4g2AaBIIGQCgIhcu3ZNnnjiCUlLS5Nx48ZJx44ds1z35ZdflgYNGmjGrly5IkOGDBGlVH63CgBFAiETgNuzWq3Sr18/iY6OlsaNG8uMGTOyXd/b21sWL14sHh7aX6Hbtm2Tt956Kz9bBYAig5AJwO1NmDBBdu3aJSIiH374ofj4+ORY06JFC3nyySd149OmTZP9+/eb3iMAFDWETABubebMmfLuu++KiIin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9NNPW53/8uXL6qabbjIrU7t2bTVjxgw1a9YsNWvWLDV79my1fPlyTdnK2Afu5kzIVEqpXbt2qdzcXJWdna2io6Ptei/k5uaqn3/+WeXn56vExEQCpzO0AAAgAElEQVTdq5RTpkxRO3bsUH5+furOO+9Uv/76q2Y5Bw8e1L1C1bdvX1VSUmL6p2PYsGFq3759mivS3377rfLz89OUf+ONN+zebp7QF7Zv364SExNVYmKi7nta5HrIHDVqlBK5/vHx8OHD1dKlS9XatWvVpEmTrH7tp2vXrio/P99qG6rTcc3VSkpKzALm3XffbfMiRteuXTXrcN9991mc35X9mJBZ5RAynQmZjz32mNkybr31VptlBg8erKnb1n9/KSkpmv+Uvby8VHZ2tkPt/b//+z8lIuquu+6yOt/EiRNN9Xz88cd2LXvFihWa9QoJCVE5OTm68+t97/Tee+9Vvr6+6quvvjKbd9euXap///6qc+fOavny5WYnclftg6SkJM0J5Pjx4zbr+te//qWp67PPPrNZzhX7wNWcDZll/fzzz3aFzPJeffVVTblevXqp4OBgtWrVKqsn1EOHDukGm3vvvVf5+fmpTz75xGrd69ev15SNioqye509rS8UFhbqfnf1RptHjRql+7Ho1atXTSFU7/XKK69Yrbc6Hddcbfbs2Wbnll9++cVmmSeffFKzrgEBARbDviv7MSGzyiFkOhMy27dvb7aMtm3b2ixT9o1242XPG3rMmDGacjc+snO0vevXr7c4z9GjR03L79ixo93LzsnJ0f04c/bs2brzr1u3TvdEYevqoKV1qux9MHPmTLP5vby8rH7d4IacnBxNXR06dFAlJSUWy7hqH7iaESFTb3vaEzL16g4ICFBxcXF21dunTx/dk92+fftsli0qKtJc0Q4MDLTaB27w1L5Q/scxItc/9rb1/i8tLbV4JbR58+aqsLBQt1x1O665UkJCgtlXGUaPHm1XuZUrV+qu64kTJzTzurofEzKrnLe4T6YTevbsaTbcr18/m2X0bvdw9epVm+UmT56sGbdixQqb5W7YtWuXnDhxQpo1aybDhw+3ON+SJUtMf9955512Lz84OFhatmypGR8fH687f7169TTjbr75Zpk/f77ddYq4bh8cPHhQs4zatWvbrCs4OFgaN25sNu748eNy5swZi2VctQ+qo+DgYKeeK1x+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hdp9Ziw1ix5LS0tKT0/XOvbLL78UfX/Ozs6UlJSkMm7ixImCGEdHR7pz547GMcvKysjHx0cQ37VrV43HARFRVlYWOTg4COInTJhAcrlcq/e9YcMG0fe9d+9ejbHdunUTxHXo0IEePXqkMfbHH38UHXfTpk1a5f284SKTqcBF5ouKi0yh2kVm9at169a0fft2un79OlVVVZFcLqebN2/S9OnTSSKRKIq96plFde7du0evvPKK2qIxPz9fKeb48eNkY2OjtliSSCT0wQcfKAqmoqIiwT6ufo0aNUplkViX/GpLT08nLy8vnYq9wMBAevz4sWh/MTExojOs+njNnj1b4z4ztD/++IMmT54syM3f359++OEHlduFiOj27dsUHh5OL7/8ssr36OTkRDNnzqTo6Gh68uQJPXz4kFJSUigkJERlTOfOnSkuLo7y8vKotLRUMV5FRQXl5ubSqVOnaOzYsSrj+/XrR3FxcZSbm0vl5eVK8efPn6d169ZRly5dVMa//fbblJSUREVFRYL3XFJSQllZWRQREaH4w6f2y87OjqKioig7O1tR8MrlciouLqYLFy6IzvRXvwYNGkSnTp2igoICpdzLysooOzubfvzxR5V/SFlbW9PmzZspIyND7X573nCRyVTgIvNFxUWmUHWR2bx5c+rbt6/oLIutra3StnNycqJ169bpNBuWkZFBPXv2VBSowNOvNkNCQkQ/VImezhB6e3uLFpfdu3enP/74QxDz2WefCT54ly9fTlVVVXrPr7aKigr6/PPPydHRUW2R5+zsTGFhYSSVSlX2NXDgQIMUmIBuX0kbQllZmcYcV65cqTK+Q4cOOr3f0NBQnbeRr6+vYryFCxfqHF/zFJL+/fvrHJ+Zman0nps3b65TfK9evYiI6NSpUzqP/dprrynGtbCw0CnWzc1NX78mZo+LTKbCZgmRge7QzMyavb09SktLFcudOnVCSkqKYnnHjh0IDg42QWamI5PJsH37dowfPx4uLi6Qy+VITU1FYmIiLl26hPz8fNy9exdOTk7w9vaGt7c3hg4dCldX1zqNV1hYiJMnT6Jx48bo3r073Nzc1LaXSqW4fPkykpOTUVhYiP/7v/9Dr169VN4yCXj6SMozZ87A3d0dgYGBcHd3N1h+YsrLy3Hs2DHExcXhzp07uHv3LpydndGiRQsMHToUw4cPh52dnc79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KRKqaZhkEgCgpKREZbu0tBSlpaVmioaIiJ5V/Oygcvy6nIiIiIgMjkkmERERERkcvy4nAICLiwvS0tKU24MHD0bnzp3NGBERET0L9u7di6ysLOW2g4ODGaOhmoRJJgEA7OzsVLYDAgIwefJk8wRDRETPjJMnT6okmfXq1TNjNFST8OtyIiIiIjI4JplEREREZHBMMomIiIjI4JhkEhEREZHBMckkIiIiIoNjkklEREREBsckk4iIiIgMjkkmERERERkck0wiIiIiMjgmmURERERkcEwyiYiIiMjgmGQSERERkcExySQiIiIig2OSSUREREQGxySTiIiIiAyOSSYRERERGRyTTCIiIiIyOCaZRERERGRwTDKJiIiIyOCYZBIRERGRwTHJJCIiIiKDY5JJRERERAbHJJOIiIiIDM7a3AEQERFVR1RUFK5duwZPT0/4+PiYOxwi+guTTCJ6LmRnZ+Py5ctIT09Hx44d4ebmBnt7e3OHRUb2+uuvY9++fcrtDz74AF988YUZIyKicvy6nIieWfHx8Rg6dCicnZ3RpEkT+Pr6YsqUKejduzccHBzQunVrjB49GleuXDF3qGQEx48fV0kwAWDdunVITk42U0REVBGTTCJ65uTn52P+/Pnw8vLCkSNHcPfuXdl6aWlp+P777+Hp6YmlS5eiqKjIxJGSMd24cUOvciIyLSaZRPRMSU9PR9euXREcHIyysjKd2pSUlGDFihXw8PDA1atXjRwhmYq3tzcsLVU/xmxsbNC9e3czRUREFTHJJKJnhhACkydPlp2psrbWfon5tWvXMHbsWOTn5xsjPDIxNzc3fPHFF6hTpw4AwNbWFqGhoWjSpImZIyMigEkmkdls3rwZx48fN3cYatXE+NavX68Sk7+/P3744QckJyejoKAA9+7dQ3R0ND799FO88MILsn1cu3YNc+fONVXIZGSzZ8/GnTt3EBcXh9u3b2PChAnmDomI/sIkk8gMnjx5go8++ggpKSnmDkVWTYwvKSkJixYtAgB069YNp06dQlRUFEaMGIGOHTvC2toajo6OeO2117B48WJcunQJAwcOlO0rJCQEP/zwgynDJyNydnbGq6++qvYPCyIyDyaZRGawa9cuPHjwwNxhqFUT49uxYwcKCwvh5uaGEydOwNvbW2P95s2bIyYmBhMnTpT9+datW40RJhER/YVJJpGJ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JCKqIRITE2WXejx16hT+9a9/AQDmzp2r11MCiIjMhXeXExGZ2c6dO7FixQqkpqbC2toanp6eGDx4MAYOHIgzZ85gxYoVKCkpgZOTE4KCgswdLhGRTphkEhGZUVFREebPn4+8vDwAQGlpKX777Tf89ttvWL58ubKepaUl/vOf/8De3t5coRIR6YVflxMRmdHZs2eVCaYm//nPf/DGG2+YICIiIsPgTCYREYAdO3Zg8+bNJh+3e/fuaNCggey1mMDTh/CvXr2az8UkomcOk0wiIgBZWVmIj483+bitWrXCl19+ibfeekuSaA4aNAgbN26Eq6uryeMiIqouJplERAA8PT0xc+ZMk4/r4eGBkSNHIjk5Gfv378etW7fQqVMn9OzZE926dTN5PEREhsIkk4gIwNChQzF06FCzjd+sWTPMmjXLbOMTERkab/whIiIiIoNjkklEREREBsckk4iIiIgMjkkmERERERkck0wiIiIiMjgmmURERERkcEwyiYiIiMjgmGQSERERkcExySQiIiIig+OKPwQAUCgUKtuZmZm4du2amaIhIqJnRWFhocq2EMJMkVBNwySTAABpaWkq24sXL8bixYvNFA0RET2rcnNzzR0C1RD8upyIiIiIDI5JJhEREREZHJNMIiIiIjI4XpNJAIBWrVqp3OizcuVKjBo1yowRERHRs2DcuHG4fPmyctvZ2Rl//PGHGSOimoJJJgEALC1VJ7WbNm2KTp06mSkaIiJ6VtSvX19l28LCwkyRUE3Dr8uJiIiIyOCYZBIRERGRwTHJJCIiIiKDY5JJRERERAbHJJOIiIiIDI5JJhEREREZHJNMIiIiIjI4JplEREREZHBMMomIiIjI4JhkEhEREZHBMckkIiIiIoNjkklEREREBsckk4iIiIgMjkkmERERERkck0wiIiIiMjgmmURERERkcEwyiYiIiMjgmGQSERERkcExySQiIiIig2OSSUREREQGxySTiIiIiAyOSSYRERERGRyTTCIiIiIyOCaZRERERGRw1uYOgIhIX9nZ2bh8+TLS09PRsWNHuLm5wd7e3txhERFRBUwyieiZEB8fj48++gjnz5/H3bt3JT9v1aoVPD098T//8z9wc3MzQ4RERFQRvy4nohotPz8f8+fPh5eXF44cOSKbYAJAWloavv/+e3h6emLp0qUoKioycaRERFQRk0wiqrHS09PRtWtXBAcHo6ysTKc2JSUlWLFiBTw8PHD16lUjR0hEROowySSiGkkIgcmTJ+PGjRuSn1lba7/S59q1axg7dizy8/ONER4REWnBJJPIBDZv3ozjx4+bOwyNalqM69evV4nH398fP/zwA5KTk1FQUIB79+4hOjoan376KV544QXZPq5du4a5c+eaKmQiIqqASSaRkT158gQfffQRUlJSzB2KWjUtxqSkJCxatAgA0K1bN5w6dQpRUVEYMWIEOnbsCGtrazg6OuK1117D4sWLcenSJQwcOFC2r5CQEPzwww+mDJ+IiMAkk8jodu3ahQcPHpg7DI1qWow7duxAYWEh3NzccOLECXh7e2us37x5c8TExGDixImyP9+6dasxwiQiIg2YZBIZUXFxMVavXm3uMDSqiTEeO3YMLVu2RHR0NJycnHRqY2lpifXr18PZ2Vnys/Pnzxs6RCIi0oJJJpERrV69GmlpaeYOQ6OaFmNWVhYuX76MZcuWoUmTJnq1feGFF7B27VpJeV5eHlJTUw0VIhER6YBJJpGRREREYNmyZeYOQ6OaGGN0dDRatWqFt956q0rtJ0yYAFtbW0n5hQsXqhsaERHpgUkmkRFcuXIFEyZMgBDC3KGoVVNjdHFxwY4dO1CnTp0qtbewsICrq6ukvH79+tUNjYiI9MBlJYkMLDc3F8OHD8fjx4/NHYpaNTnGwYMHV7uPLl264Pfff1cpa9myZbX7JSIi3THJJPrL6dOnERsbi6tXryIxMRFOTk7o2rUrunbtCg8PD7i7u8PS0hKrVq3CrVu3sHnzZkkfCQkJGD16tOz1f3/++Seys7Ml5Q0aNICdnZ3G2GJiYhAfH4+bN28iNTUVt2/fRsOGDdGkSRM0bdoUvr6+GDp0qE6zdcaKsaSkBLGxsYiIiEBycjKysrKgUCjQtWtXdOvWDd26dUOfPn3g4OCgNcbqatGihcq2paUlWrVqZfRxtXn48CEKCwt1rq/tuGvrz9raWvbGqbS0NISGhuLAgQMYP348li5dKqnzxx9/4MiRIzh//jwuXboEFxcXuLq6YsqUKXB3d9cYd2lpKaKionDw4EHcuHED9+/fR6tWreDt7Y0ZM2aofa6pPgxxTpSUlODevXt6jevi4gILCwsAQFlZmdplTsvVq1cPjRo10msMoueGoFrJ1tZWAFC+OnfurLIdFhZm7hBNJi8vT0yePFm573Xr1hXt2rUTDRo0UDkmjRo1Eq6ursLCwkKMHDlSpY/79++LBQsWiPr166u00eX1ySefqI3t6NGjwsvLS6X+Cy+8IKytrSX92Nvbi4ULF4qSkhLZvowVY1lZmQgJCREuLi5a+2natKmIioqq2hulh6lTp6qMO3DgQKOPqYuAgACdj7mFhYVYvnx5tfrr2rWrsm5ZWZmIjIwU/v7+wtLSUlln1qxZKn0+fvxYzJ8/X9SpU0e2T0tLSzFlyhRRVFQkG9PBgwdF06ZN1cZkZ2cnvvvuuyofQ0OeE3FxcXqfC48fP1a2T0lJ0Vp/xIgRVd7XZ0XPnj1V9rl3794q20FBQeYOkcxjE5PMWopJ5lOpqamicePGymNw+PBhUVpaqvz57du3xapVqyQJZ+Ukc/v27aJJkybKl6Ojo+wHXsU65a/PP/9cEldZWZl44403VBLfZcuWiby8PCGEEEVFRSIhIUH4+/tLxhk0aJDKPhgrRiGEyMrKUn7gW1lZienTp4tdu3aJhIQEER0dLYKCgkTDhg0l40yZMkU8fPiwOm+dRkOGDFEZLzQ01Ghj6ePBgwdi27ZtokuXLmqTEgcHBxEeHi6ys7O19qdQKER2drZYsmSJSh+Ojo7iq6++Evfu3RMZGRli+fLlomXLlrLjVUwyT58+LV566SWdkq1JkyapxFJUVCRmz56tU1srKyvx/fff63XsjHFOCCFEYmKiWL9+vfDx8dEY8/Lly0VmZqak/Y0bN8Q777wjqT9q1Cjx9ddfiz///FOv/XwWMckkNZhk1lZMMp9+QPfv318AEB07dhT3799XW/fOnTuiXbt2apPMyiIjIyUfOtu2bdM5tg8++ECl7ZYtW9TWnT59umSsdevWaR2jujFev35dtG3bVgAQHTp0EGfOnJGtl5KSIpydnSVj+fr66jyWPkpKSkSjRo2U49jZ2Yl79+4ZZayqunfvnnB1dZVNZhYuXKh3fxkZGcr2tra24ty5c0KIp7OK1tbWamclKyaZJ06cEHZ2dgKA6NWrl/jnP/8pgoKCRL9+/US9evVk2+7atUsI8fSYjx49WlnesGFDMWTIEDFhwgTRoUMH2bYtWrQQBQUFOu+jKc6JOXPmyMZqaWkp8vPz1bb7/fffVer/+9//1nm/ngdMMkkNJpm1FZNMITZu3Kjc3+3bt2utf/r0aWFlZWX0JDMiIkKlXYMGDdTOwgghxM2bN5Vxlb9atmypdZzqxJifn6/8nXF0dBQZGRka6588eVI2Udm9e7dO4+kjNjZWZYwVK1YYfAxDSEpKEnXr1pUcE3WzxpocO3ZM2X7OnDnK8ocPH4pTp06JwsJCkZKSIjtLOWvWLBETEyNsbGxE//79xYULFyT9//7777KXQ/Tp00eUlZWJCRMmCABi+PDh4uzZs5Kvp48cOSJsbGwk7deuXavT/pnqnBBCiPHjx8smmpq+4p87d67e+/Q8YZJJamziI4yo1jpw4IDy361bt9Za/5VXXsG8efOMGNFTERERKtvNmjWDlZWV2vqtWrXCa6+9plJ269Yt5OTkGCU+APjggw+QmJgIANiwYQOaNm2qsX7v3r3xxRdfSMrnzZuH+/fvGzS2/fv3K//dpk0bfPjhhwbt31A6deqEBQsWSMp//vlnvfs6e/YsgKePb/p//+//Kcvt7e3h7e2NevXqoW3btpg1a5ak7enTp/H6669jw4YN+Omnn+Dh4SGp4+npiWPHjknKT548ibFjx+LAgQPYs2cPDh48CC8vL1hbq95TOnjwYHz55ZeS9lFRUTrtnynPiW3btqFDhw6S8g8//FD2aQwJCQnYtGkTAGDy5MmYO3eu1jGIagsmmVRrXbx4Ufnv8oRJm3fffddY4Sj9+uuvKtslJSVa28jdOX379m2DxVRRYmIiQkJCAACurq5q1wuvLCAgAJaWqv/l5OTk4N///rfBYsvLy8POnTuV259//nmNfj7m4sWL0bx5c5WyQ4cO4c6dO3r1s2fPHgDAoEGDZBOkcnLPD01KSsK+ffswbdo05V3Tctzd3fHqq69KyuPi4hAbG4s333xTY4wjRoyQ3FV+7tw5KBQKje0A054TDRs2xLfffou6deuqlN+6dQsLFy6UxDF58mSUlJTAy8sLW7du1do/UW3CJJNqrYqPLtmwYYNOH1zt27dHz549jRkWunfvrrLdt29frW3kll/Mz883WEwVVZyR7N+/v87tHB0d8dJLL0nK4+PjDRIXAGzcuBFPnjwB8DThGjVqlMH6Nob69esjMDBQpaysrAwbNmzQuY+ff/4Z165dAwDMnDlTY93GjRtLyqZMmQIfHx+dxvLz85OUrVq1Cl5eXlrbWltbo1evXipljx49wp9//qm1ranPCQ8PDyxfvlxSvnnzZpw6dUq5vWTJEly8eBFOTk7Yt28f6tWrp1P/RLUFk0wiANevX8fo0aN1+hDSNmNTXZMnT4aNjQ2Ap0nIpEmTtLaRSx7Kky1DKigowO7du5Xb/fr106t9s2bNJGVJSUnVjgt4uub5unXrADydjSr/CrOmmz59Ol588UWVsi1btuh8GcG2bdsAPD22w4cP11hXblZX0+xlZS4uLpKy0tJSndvLPbPz0aNHWtuZ45wIDAyUJLNCCLz33nsoKSnB8ePHERwcDAsLC4SHh8v+AUVU2zHJpFqrcsITFRWFXr164fjx4xrbzZkzB999953R4ho8eDBu376Nr776ChkZGRg0aJDG+kII2WvNiouLDR7buXPnVB7+/f7778PFxUXnV+VVeICnX0MaIiGePXs2Hj58CAsLC+zatQudOnWqdp+mYGdnhzlz5qiUPXr0COvXr9faNjc3V3kN6rRp0yTXQlZW1aU6y8klmWVlZTq3l3sIuy5JpjnOCUtLS4SHh6NBgwYq5VeuXMHixYvx1ltvQQiBDz/8EP7+/jr3S1SbcMUfqrXefPNNyfWAV65cwWuvvYZhw4bhk08+gaenp6SdhYWF1g/z6nJycsL48eM11klLS0N4eDjCw8Nx48YNo8ZTLi4uTmW7ffv2BlnBJycnB23atKly+6ioKGXiv2zZMowcObLaMZnSP//5T6xZs0Yl4dqwYQM+/PBDNGzYUG27sLAwFBcXw8rKCtOnT9c6TnV/b8tnE6tK7utkXS5TAcxzTrRp0wbr1q3DtGnTVMrL/9/w9vbGZ599Vu1xiJ5XTDKp1lq4cCF27Ngh+7VkVFQUoqKi4O/vjyVLluCVV14xQ4RSCoUChw8fxpYtW3D48GEoFAq0aNECPj4+OHHihNHHv3r1qsr2kiVLMGLECKOPq0lmZqYyCRg9ejQ+/vhjs8ZTFY0aNcLMmTOxZs0aZdn9+/exefNm2TvQgaezdeU3YA0bNkyylKYcTXdkP6uMfU5MnToVBw8eRGRkpEq5tbU1vvrqK6P/wUn0LOPX5VRrOTk54ZdfftH44Xzo0CF4e3vD19cXCQkJJoxOVWFhIbZu3YqXX34Zw4YNw6FDhzBo0CAcOHAAN2/e1DrDYyiV13k21QyqOqWlpXj99deRnZ0NNzc3hIeH63WNYU0yb948yTWTa9euRUFBgWz9mJgYpKSkAABmzJhh9PhqGlOeE6GhoZLrZktLS1Ueg0ZEUkwyqVZzc3PDmTNntN4lHRMTgx49eiiv+zOVsrIyhISEoF27dpg5cyZSUlIQEBCAixcv4tixYxg1apRJZ6cqz/qaO8kMDAxEXFwcGjdujIiICMn1c8+Sxo0b45133lEpy8nJQWhoqGz98sfltG3bVvau7+eVOc6Jxo0by14ju3jxYly5csWgYxE9T5hkUq3XvHlzxMbGYt++fRqvCywrK8PGjRvRq1cvvZ9jWBVpaWkYMGAA3nvvPWRkZKB9+/aIjY3F3r170a1bN6OPL6fyrJo5k8zt27dj/fr1cHBwwNGjR6t1TWdNERQUJPn6NTg4WHLDSmZmpvIB5e+++67k+aPPK3OeEwcPHpSUFRUV4a233jLKTXZEz4Pa8T8TkQ7GjBmDpKQkrFq1SuPNLMnJyejfvz8yMjKMFktERATc3d1x8uRJAMCAAQOQkJCg9yODDM3R0VFlu/zrWlOLjIzEjBkzYGNjg8jISLi7u5slDkNr3bq15BFZ6enpCA8PVynbsWMHSktLUbduXcns5/PKnOfE5s2b8e2338LZ2VnykPeEhIRn8jpgIlNgkklUQb169bBgwQKkpKRg7ty5klU/yqWkpKi9IaO64uLiMG7cOOXX8p07d0ZkZCTs7OyMMp4+Kj+CJjk5WbmkYVUJIfSqf+bMGQQEBMDCwgLffPONTg/mfpYsXLhQcl3pqlWrlI8KUigUyq/Qx4wZA2dnZ5PHaGrmPCfi4+Mxb948WFhYYPfu3fjf//1fyfsTHBysTH6J6P8wySSS4eTkhLVr1yI5OVntM/n27NmDy5cvG3Tc3NxcjBw5EkVFRcqy5cuX15hrDeWW6ps/f36V+yssLET37t2RmZmpU/2kpCQMGzYMBQUFCAsLwz/+8Y8qj11Tubq6SlYqunHjBvbu3QsA+PHHH3Hr1i0AteOGH3OeEw8fPsS4ceNQVFSExYsXw8/PDwMGDMDs2bNV6ikUCkyaNEmnZ34S1SZMMqnWatasGR48eKCxTuvWrXHs2DGsXLlScjOBEAI///yzQWMKCQnB3bt3ldtWVlZVftCzvjOEuujTp4+k7JdffkFUVFSV+tu+fTsyMzNlV2epLDk5GT4+PsjLy8OmTZt0XjP9WbRo0SJJ2cqVK6FQKJQr/HTp0sXsl0+YgjnPialTpyIlJQX9+vXDJ598oiz/7LPPJA/7T01NlTxUn6i2Y5JJtVZmZqbsxfyVWVpaYtGiRVi4cKHkZ4ZaErHckSNHVLadnZ2rvB6yPiux6Kpv376yjwhatGiRXssLAk9nMVevXo3hw4drvXHljz/+gI+PD7KysvDpp5/i/fff12uscmfPnsWPP/5Ypbam1KNHD/j6+qqUJSUlYd26dcr433vvPXOEZnLmOic2btyI/fv3w9nZGXv37lX5I9PGxgbh4eGSPzzDwsLwww8/VCk2oucRk0yq1fRZHnLZsmVo2rSpSlmjRo1k68olTbo8+uj27dsq2zk5OWqfk6iNtg/UqsTo7OwsO4t05coVBAQE6Lx6i0KhwMSJE5Genq51dZ7r169j4MCByMjIwEcffYTFixfrNEZlly5dgp+fn+QY11Ry+xkUFASFQgFbW1ud1u9+0kserQAAIABJREFUHpjynCh3/vx5BAYGKq/DrLwELQB4eXnJzji/++67yM7OrlJ8RM8bJplUq0VHR+t8PWCdOnXQo0cPlTIvLy/Zuvb29pIyubWUASAxMVG5dnflu7cVCoXWr+QLCwsRGxsrKa+8HnjlSwOqGmNgYKBsnf3792Po0KHK6wXVKS4uxowZM7B//3506tRJMmNXUXmCeefOHQQGBmLFihUa+5YjhEBMTAz+/ve/o6ioCOPGjdO7D3MYMGAAvL29VcoUCgUAYPz48VVazlNutlmfGejy8SvSZ3ZQrr22P0xMeU4AQF5eHsaOHYvi4mIEBgZqfAbpxx9/DDc3N5Wy3NxcTJ482SiXqxA9a5hkUq1WXFyMiRMnyn74yak8c9mzZ0+d6gFPE9rKdu/ejYEDByq/gnZ1dZXUmTdvntrn8P3222/w9PTEnj17JD+rOAN04cIFycxXVWPs37+/2qUkjx8/ji5dumD+/Pm4dOmSygftnTt3EBoaig4dOiA0NBTW1tbYtWuX2jv4L1++jL59+yI9PR3e3t7o168fIiMjNb4iIiKwf/9+fP3119i2bRuWLFmiTGTv3LkDf39/SdJSk6mbta3qDT+PHz+WlFVOvDSRSwj1mVWUS2i1jW/Kc6KsrAxvvPEG0tLS4OHhofWPmjp16iAkJETyrcCRI0dUlgglqrUE1Uq2trYCgPLVuXNnle2wsDBzh2h0Ffd32bJlWusrFArRoUMHZZvx48errVtYWCjq16+vMgYAsXLlSlFSUiIePHggwsLChI2NjVi9erWy3dq1ayVtAIhXXnlFpKamCiGEKCsrE2fPnhVz5swRVlZWwsHBQUyYMEHSpl27duLy5cviyy+/FPb29mLBggUGiVEIIfLy8sRLL70kG2vFV926dUXLli2FnZ2d5GdLly5Ve/zOnDkjHB0dtfav7+v777/X+j7XJAqFQnTr1k1lH3r06FHl/g4ePCg5Jn5+fjq33759u6R95d8rTd5++21J+z179mhsY8pzYt68ecq6p0+f1nm/AgICJGNZW1uLkydP6tzHs6xnz54q+967d2+V7aCgIHOHSOaxiUlmLcUkUzXJtLS0FHPnzhWPHz9WW3/VqlXK+u7u7uLJkyca+x8xYoTsh6ONjY2oU6eOACA8PDxUxiwoKBBt27ZVmyS1b99evPjiiyoJx40bN8RPP/2kMbny9/cXZWVlBomx3MWLF0WTJk2qlOy9+uqrori4WPa4nThxQjRo0MDgCaaTk5MoKirS+J7VRHv37lXZj+3bt1e5r3HjxkmOi729vUhPT9fatqCgQHTt2lXS3sPDQ+u5IIQQt27dEg0bNpS0Hzt2rFAoFBrHNcU5sWLFCuXPrKysREpKim4HVQjx+eefy47h6Ogo4uPjde7nWcUkk9RgkllbMckUsh8KrVu3Fjt37hRXr14VpaWlQqFQiOvXr4upU6cKCwsL5Yda+QyKJnfv3hUvv/yyxg/HrKwsSbtjx46JunXravyAtLCwEB988IEyacrLy5O8p+WvYcOGqU2eqxpjuZSUFOHq6qpXsufr6ysePXok219ERITs7KohXjNnztT6ntVEpaWlon379gKAaNSokU4JXUUPHjwQiYmJYtasWWqPTZcuXUR0dLTIzMwU+fn5yrZFRUUiIyNDnDx5UowcOVJt+759+4ro6GiRkZEhCgsLVdqfO3dOBAcHS2ZkK77efvttER8fL/Ly8mT3wZjnxK+//irGjx8vqePt7S2++eYbtb+rQghx8+ZNsXnzZtGsWTO1cTk4OIjp06eLgwcP6v3ePSuYZJIaTDJrKyaZT5PM5s2biz59+sjOsNjY2KgcJwcHBxEcHKzXbFhqaqro2bOnMkEFnn6NNmvWLLUfqEI8nSV0c3OT/SD19PQUv/76q6TNypUrVera2tqKpUuXitLSUqPEWK6oqEh89tlnwt7eXmMS4OjoKDZu3ChKSkrU9jVgwACjJJiAfl9/1iQVL9OYNWuWXm2zs7P1Pk7u7u7K9vPnz9e7fcXLSPr166d3+7S0NNl9McY5UVBQoDWe5cuXqz2+HTt21GvfgoOD9Xr/nhVMMkmNTRZC8Ba42sjOzg75+fnK7c6dOyMxMVG5HRYWhsmTJ5shMtMJDQ3F6NGj4eTkBIVCgaSkJJw+fRoXLlxAVlYWcnNz4eDgADc3N7i5ucHPz6/KS/jl5OTgxIkTaNSoETw9PeHi4qK1TUlJCS5evIiEhATk5OTgb3/7G3r16qX2sUnA0+X3Tp06hSZNmsDX1xdNmjQxaowVFRYW4ujRo4iOjkZ6ejpyc3Ph6OiIFi1awM/PD0OGDIGtra1efRIQExOjvAP/6tWr6Ny5s5kjMh9TnxOkGy8vL5w7d0653bt3b8TFxSm3g4KCeCNU7fQfJpm1FJNMomfDmDFjcODAAfTr18/gK0wRGQKTTFLjP3yEERFRDZWRkYGIiAgAtWOdciJ6vjDJJCKqoUJCQlBaWgoXFxeMGTPG3OEQEemFSSYRUQ30+PFjbNy4EQDwzjvvqH1oPRFRTcUkk4ioBtqwYQPu3bsHOzs7zJkzx9zhEBHpjUkmEZGZJCYmyi71eOrUKfzrX/8CAMz9/+zdf3zP9f7/8ft+YUOsZUKOlBXzM2cjrSgiLorkOMeKDoqjvkVhkjOn06Hyo/yMZGn5lTDC4cSJU/r4McmPyq/F/F4bMobNfr5f3z+6eF+8vd7b3u/ttb03u10vl10uXs/38/l6Pl6z5t7z9ev117kjGkC55OvpAgCgovn00081YcIEHT9+XL6+vmrdurWeeOIJPfbYY4qPj9eECROUk5OjoKAgRUVFebpcACgSQiYAlKKsrCyNHj1aFy5ckCTl5ubq+++/1/fff6/x48fb+3l7e2v27Nm67bbbPFUqABQLp8sBoBTt3LnTHjALMnv2bP3lL38phYoAoGSwkgmgwpk/f77mzJlT6vN27NhRY8eOVbVq1ZxeiylJfn5+mjRpEs/FBFDuETIBVDgpKSnas2dPqc/boEEDBQYGatGiRerfv78paHbq1EmzZs1SkyZNSr02ALAaIRNAhdO6dWu99NJLpT5vq1atJElPP/20EhIStHLlSp06dUqNGzdWeHi4WrRoUeo1AUBJIWQCqHC6deumbt26ebSGunXr6tVXX/VoDQBQkrjxBwAAAJYjZAIAAMByhEwAAABYjpAJAAAAyxEyAQAAYDlCJgAAACxHyAQAAIDlCJkAAACwHCETAAAAluONP5AkpaenO2x/8cUX+umnnzxUDQCgvDh9+rTDdlZWlocqQVlDyIQk6dy5cw7bGzdu1MaNGz1UDQCgvEpLS/N0CSgjOF0OAAAAyxEyAQAAYDlOl0OS5Ofnp2vXrtm3fX195ePj48GKAADlQXZ2tgzDsG/7+hIt8Dt+EiBJuuuuu3Tw4EH7dkxMjAYMGOC5ggAA5UKbNm20a9cu+/btt9/uwWpQlnC6HAAAAJYjZAIAAMByhEwAAABYjpAJAAAAyxEyAQAAYDlCJgAAACxHyAQAAIDlCJkAAACwHCETAAAAliNkAgAAwHKETAAAAFiOkAkAAADLETIBAABgOUImAAAALEfIBAAAgOUImQAAALAcIRMAAACWI2QCAADAcoRMAAAAWI6QCQAAAMsRMgEAAGA5QiYAAAAsR8gEAACA5Xw9XQAAwD2//vqrli5dqurVq2vIkCGlPh4AXMFKJgCUA4ZhaNOmTerdu7caNGigUaNGKSYmptTGA4C7WMkEgDIsNTVVn332mebOnasjR46U+ngAKCpCJgCUQampqRoxYoSWLVumzMzMUh8PAMXF6XIAKINq1KihwMBAzZ07V4sWLdK9995bquMBoLhYyQSAMsjHx0fTpk2zb3t7e+u5554rtfEAUFysZAJAOdC1a1ePjgcAdxEyAaAcCAwMlJeXl8fGA4C7CJkAUA54eXmpcuXKHhsPAO4iZAJAOeHn5+fR8QDgDkImAJQT3t7F+5Vd3PEA4A5+4wBAOVHcayq5JhNAaSJkAkA5QcgEUJ4QMgEAAGA5HsYOVCA5OTlKTU11a0xwcLB9BSwvL0+//fZbgf0rV66smjVrFrnG0nD48GGtXr1au3fvVnJystLS0tSgQQOFhIQoJCREzzzzjO68884i7z8nJ0fffvut1q5dq4SEBKWkpMhms6l58+Zq0aKFWrRooYcfflg1atSw8KhKx44dO/Ttt9/qwIEDOnjwoIKCgtS8eXM1b95crVq1UsuWLeXt7a2JEyfq1KlTmjNnjqdLBuAhhEygAtm1a5ciIiLcGnP16lVVrVpVknTy5MlCX0/Ys2dPrV69usg1lqTt27drxIgR2rlzp+mz/fv32/88evRojRgxQlFRUapevbrL+7fZbJo/f76io6N17tw50+cHDhzQF198IUmqU6eOYmJi1L179yIcSelLTU3VyJEj9dlnn0mSKlWqpPr16+vIkSPatGmTvV/NmjVVp04dHT58WD179vRQtQDKAk6XAxXIQw89pIMHD2rGjBnq2LFjgX3Hjx+v5ORke8CUpHvuuUfHjh3ToEGDTP179eqlL774QosWLbK87uLKycnRkCFDFBERoZ07dyokJESTJ0/WN998o5SUFO3bt09vv/22qlWrJklKT0/X+PHjdf/99+vo0aMuzXH27Fm1a9dOQ4YM0YULFzR48GAtXLhQ+/bt09dff20KrMnJyXryySc1aNAgXb58uUSO2yonTpxQaGioPvvsM4WGhuqrr75SRkaGjh49qitXruj06dOaOHGiqlWrpkuXLunQoUMyDMPTZQPwNAMVUkBAgCHJ/hUaGuqwHRsb6+kSUQqGDx/u8Pd+/cvb29vIyMjId9zu3bsd+r///vulWLV70tLSjMcff9xea48ePYxLly457bt27VrT9+Lee+81zp49W+AcR48eNe655x5DkhESEmLEx8c77ZeYmGjUqlXLNEfnzp1dOpbAwECHcWFhYS6NK854m81mdOjQwZBk3HfffcbFixfz7ZuUlGTce++99v0//fTTbtWH8ik8PNzh5yoiIsJhOyoqytMlwjM+ZCUTqMCmT5+uyMhIU7vNZtP69evzHbd48WL7n6dOnaqRI0eWSH3FZbPZ1Lt3b/vp3E6dOmn16tX5Xgv51FNPqXnz5g5tiYmJevnll/Od49q1a+rRo4eOHTumwMBAbdmyRW3btnXa95577tGXX35pevPO119/rSVLlrhzaKVm9uzZ2rJli6TfLyMo6HrbunXravHixfLx8Smt8gCUYYRMoIL7+OOPFRISYmofOXKkrl69amrft2+fPvzwQ0nSgAED9Prrr5d4jUU1btw4e8D08vLSpEmTCn2MzyOPPGJq27Bhg7Kyspz2f+2113Tw4EFJ0syZM1WnTp0C9x8REaFp06aZ2keMGKGLFy8WONYTVq1aZf/z3XffXWj/Bx98UCNGjCjBigCUF4RMoIKrXr26li9frkqVKjm0nzp1SmPGjHFoy8nJ0YABA5STk6M2bdpo7ty5pVmqWw4cOKD33nvPvt2nTx/98Y9/LHRcixYtTG3p6ek6efKkqf3gwYOaN2+eJKlJkybq16+fS7X17dvX9Padc+fO6f3333dpfGn68ccf7X++HqYLM2TIkJIqB0A5QsgEoFatWmn8+PGm9jlz5mj79u327ejoaP34448KCgpSXFyc6bRvWfLOO+843HwyevRol8b16tXLdDr9gQce0H333Wfqe+OKZIcOHVyuLTAwUPXr1ze179mzx+V9lIbk5GSHR17NnDlTOTk5hY5r1KiRwsPDS7I0AOUAIROAJGnUqFGmU8WGYehvf/ubcnJytHnzZk2ZMkVeXl5asGCB05BUViQlJWnZsmX2bS8vL4WGhro0Njg4WD/88IMGDBigiIgI/f3vf9fy5ctN/a5du+ZwbWr79u3dqrFu3bqmtkOHDrm1j5J2c9g+evSonnnmGWVkZBQ69tlnny2psgCUEzwnE4AkydvbWwsWLFCLFi0crsXcv3+/xo4dqyVLlsgwDI0aNarMP9tx06ZNstls9u26devK39/f5fGNGjVSbGxsgX127dqlzMxM+/bLL7+s4cOHuzxHWlqaqe3UqVNKT093eGyUJwUEBKhu3br69ddf7W3r1q1T27ZtNX36dHXq1CnfscOHD9crr7xSGmUCKKMImQDsGjZsqOnTp+vFF190aL9+rWC7du0crnMsqzZv3uyw3ahRI8vn2LZtm2kOK97gc+7cOTVs2LDY+7HKs88+a7pWdP/+/Xr88cf15JNP6u2331br1q1N47y8vOTryz8xQEXGbwAADl544QWtWbNG//73vx3afX199fnnn5eL4PDzzz87bN91112Wz3HgwAGH7ejo6FvyDTdjxozR/Pnznd75vm7dOq1bt07du3dXdHS0HnzwQQ9UCKCs4ppMACYxMTG64447HNpyc3MdHmdTlt38fvWzZ89aPsfN74A/duyY5XOUBUFBQfruu+8KDOrr169Xu3bt1LlzZ+3bt68UqwNQlhEyAZjUrl1bM2bMMLWPHTvW4R3fZdXNIbMkAuDNK3u3asiUpGbNmik+Pr7QO+g3bdqksLAwDRs2zOk1pwAqFkImAKfWrFljasvKylL//v2VnZ3tgYpcZ9z03uxTp04pLy/P0jmuXbvmsH0rh0xJqlevnr799lvFxcUVeM1oXl6eZs2apbZt2yopKakUKwRQ1hAyAZjMmTNHy5cvV61atdSgQQOHz/bt26d//OMfHqrMNbfffrvDdm5urk6fPm3pHIGBgQ7biYmJlu6/rOrdu7cOHTqkiRMnFnijU0JCgjp06OBwZzqAioWQCcDBnj17NGLECHl5eWnx4sX67LPPTK9inDJlirZu3eqhCgt3cwCUnK/MuuPEiRMO2zcH2YSEBO3cubNYc9y8AltWVa5cWW+88YYSExP1+uuvm94WdV1iYqLeeOONUq4OQFlByARgl5aWpj//+c/KysrS2LFj1aVLFz366KMaNmyYQz+bzabnn39eV65c8VClBbv//vtNbTNmzCjyKfP//e9/pmeD3rzCK7n+ViFnMjMz9cADDyg5ObnI+yhtQUFBmjp1qhISEvJ9ZuaSJUtMd/sDqBgImQDsXnjhBSUmJqp9+/Z6++237e3vvfeeGjdu7ND3+PHjbj18vDRFRESY2o4fP64vv/yySPuLjo5Wnz59HNoefvhhU7/vvvtO69atK9Icn3zyiZKTk1W7du0ijS8JmZmZqlu3ri5dulRgv7vvvlv//e9/9e6778rHx8fhM8MwtGXLlpIsE0AZRcgEIEmaNWuWVq5cqVq1amnp0qUOYcHf318LFiwwBYjY2FitXr26tEst1M2vx7zugw8+cPuU9JdffqkdO3aob9++pjluvoxAkt58803l5ua6NUdmZqYmTZqkHj16yNu77PxaNgxDycnJLl1q4O3trTfffFNjxowxfVbWXpcJoHSUnd9mADzmhx9+0KhRo+zXYTp7r3abNm305ptvmtqHDBlSIs+hLI42bdqoRYsWpvb4+HhNnDjR5f0cPnxYAwcOVIsWLUwrubVq1XL6es39+/erb9++ysnJcWkOm82mfv366cyZM3r66addrq00rVixwuW+b731lurUqePQVrNmTatLAlAOEDKBCu7ChQv605/+pOzsbI0aNUpdunTJt+8//vEPNWvWzKHt/PnzGjBgQJm7aSW/U/nR0dH65JNPCh2/Z88ede/eXWlpafrrX//qtM+oUaOctq9cuVLdunXTqVOnCpwjOztbQ4cO1cqVK9W4cWN17ty5wP43B1d3V0yLOv7rr792+VpRPz8/hYWFObS1adPGtQIB3FIImUAFlpeXp7/85S86efKkWrVqpQkTJhTY38/PT/PmzTOd0t2wYYMmT55ckqW6rX///qawI/2+cjh48GD169fP6YPlz549q1deeUXh4eE6duyYHnnkEdONT9d1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N3Tr1g3dunWzuE+vXr3Qq1cvO1ZFRGQf/OIPEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1rloXQFVDTk6OZHnhwoX46KOPNKqGiIiqC51OJ1nOy8vTqBKqahgyCQBw//59yXJ6ejrS09M1qoaIiKqrgoICrUugKoKny4mIiIhIdQyZRERERKQ6ni4nAEC9evXw8OFDw/JTTz0Fb29vDSsiIqLqQKfTSd4/PDw8NKyGqhKGTAIA+Pj44NatW4bl999/H5GRkdoVRERE1UJISAjOnTtnWPby8tKwGqpKeLqciIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIda5aF0BE1Udubi4KCwstXt/Lywuenp42b8/V1RU+Pj6y9tTUVGzatAn79u1DeHg4Fi1aJFvn6tWrOHjwIH788Uf89NNP8PPzQ0BAACZOnIigoCCTdZeWliI+Ph6xsbG4du0a7t69ixYtWqBHjx6YOnUqGjZsaLK/JY4ePYrExETcuHED169fR1paGurWrQt/f380btwYAwcOxNChQ1GnTh2j2ygpKcGdO3esGtfPzw9OTk4AgLKyMvz+++8m169duzYaNGhg1RhERAAAQTWSh4eHAGC4dejQQbK8ZcsWrUukKmj8+PGS54mpm5OTk4iOjq7U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lsLPzw/PP/+81uUQEZEDMWQSkV3k5eVh7dq1AIDXXnvNoms2EhHRk4Mhk4jsYs2aNbhz5w48PT0xY8YMrcshIiIHY8gkIpulpKQoXofx5MmT+N///V8AwKxZsyTfiCYiopqB3y4nIqt99tlneP/993H9+nW4uroiODgYgwcPxrPPPovTp0/j/fffR0lJCXx8fBAVFaV1uUREpAGGTCKySlFREebMmYOcnBwAj35+8OzZszh79iyio6MN6zk7O+OTTz5BvXr1tCqViIg0xNPlRGSVM2fOGAKmKZ988glefPFFB1RERERVEUMmEVklMDAQXl5eRu93c3PDypUreV1MIqIajiGTiKzi7e2N7du3KwbNAQMGIDk5GbNmzdKgMiIiqkr4mUwistqoUaNw5coV7N27Fzdv3kT79u3RvXt3dO7cWevSiIioimDIJCKbNGnSBNOmTdO6DCIiqqJ4upyIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESq489KEgDgt99+kyxPnjwZU6dO1agaIiKqLoqLiyXLd+7c0agSqmoYMgkAUFJSIlkuLS1FaWmpRtUQEVF1xfcOKsfT5URERESkOoZMIiIiIlIdT5cTAMDPzw+pqamG5cGDB6NDhw4aVkRERNXBrl27kJWVZViuX7++htVQVcKQSQAAT09PyfL48eMRGRmpTTFERFRtnDhxQhIya9eurWE1VJXwdDkRERERqY4hk4iIiIhUx5BJRERERKpjyCQiIiIi1TFkEhEREZHqGDKJiIiISHUMmURERESkOoZMIiIiIlIdQyYRERERqY4hk4iIiIhUx5BJRERERKpjyCQiIiIi1TFkEhEREZHqGDKJiIiISHUMmURERESkOoZMIiIiIlIdQyYRERERqY4hk4iIiIhUx5BJRERERKpjyCQiIiIi1TFkEhEREZHqGDKJiIiISHUMmURERESkOletCyACgPj4ePzyyy8IDg5G//79tS7HLC3rrS6PVUZGBnbt2oW6detiypQpWpdjN9VlPoiIHI0hkzQ3duxY7Nmzx7A8c+ZMfPzxxxpWZJqW9Vb1x0oIgWPHjmHDhg2Ii4tDaWkpunXr9sSGzKo+H0REWuLpctLUsWPHJG/SALBq1Spc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kderV8/U5sozxUtSUsh01/dWlcexa9euDo+VlKTvvvtOq1at0sCBA8s1liR98sknOnPmjNO1OgFUPUImcB1JTEzUwoULNW3aNHvbunXrHPYJCwtT3bp1KzR+UVFRpeqrKh999JGeeeYZRUVFafHixfL29tYNN9yge++91/K5nJ1JrMyZtJICqju+t6o+jt26dZOHh4cp9E6aNEn9+vWTl5fr/8nKzc3V9OnTNWjQoBp7PS1Q2/E3D6ghSgoTZT2q0VU2m00jRoxQXl6ehgwZYm9PTk522C8tLc10l6+ramLIXL58uZ555hkZhqFJkyZV+tGWZQkICDC1nTp1yqW+qampprarH7N4ter+3qrjOIaFhTk9G7t//37Fxsa6fEbYZrNp+PDhSklJcfizDqB6ETKBGiIzM7Nc7eX16quv6vvvv9ddd93lcH1c8Tt6bTabtm7dWupYubm52rJli6k9OzvbYfvChQumfsUVFhaWUbljbVcrK3SkpqZq+PDh9jNjzq75s1pERISpLSEhocx+iYmJ6tKli6m9+DG8ojq/t+o8juPGjXPavnTpUvXv39+0uHtx+fn5GjNmjJYuXarbbrtN0dHRVVEmABcQMoEa4sodwsUdPXq0UuMahqEJEybojTfekCQ99NBDDu9HRkaa+rzwwgslnkGNj4/X3Xffrc8++8z03tVn137++WfT2orFn+gimddGLE3xQFo8HBX30UcfKSsry7791FNPafny5Tp9+nS5wm15tG/f3nTzT1xcXKnL8WzatEldunRx+nzto0ePOqw1eUV1fm/VeRx79OhR4qMkN23apNtvv10TJkzQ3r17HX5WP3XqlObOnavWrVtr7ty58vLyUlxcnHx8fCytD4DrCJlADREfH++0ff78+SUG0NLYbDZ988036tSpk2bMmGFvLx4yO3ToYOp76NAh9ejRw74Gos1mU3x8vMaOHavOnTvr9OnTGjZsmKnfwoULtX//fi1atEg9e/ZUmzZtHN5PSUkx9XH2bOuSXH2ji/Sfn3lLu95xz549DtsJCQkaMmSIbrzxRnl7e8vPz08hISFq0qSJWrVqpcjISN1zzz3q3r27HnzwQQ0bNkwvvviiPv/8c6dBz5nAwEB17NjRoa2goEBDhgzRjh07HNq2bNmiJ598Ug888IDuvPNOrVixwjReTk6Opk6dquzsbB0/flybNm2SVL3fW3Ufx/nz56tZs2ZO38vKytKMGTPUrl07+fr6qnnz5vL391fTpk01evRo+5nOSZMmOT1GAKqRcQ1LS0szJDm8QkNDHbY3b97s7jKBMu3bt88IDg42/Xm+8mrRooUxcuRIY8qUKcbrr79uTJs2zXjnnXfsrzfffNOYPHmyMXbsWOPpp582unbtatSvX980zs0332yaOycnx2jVqlWJc0dERBgNGza0b0dFRRnHjh0zNm/eXGIfScaAAQOMoqIih7nat29v2u+WW24xMjMzyzxGS5YscTrP3//+9xL7fPTRR6XWWJ5XnTp1jEGDBhlnzpwps9avv/66xHFuuukmo1u3boa/v7+9rXfv3sbly5cNm81WYj9PT09DktGhQwejqKioWr83dxzHPXv2GOHh4RWao3PnzkZ+fn6Z3xPgTjExMQ5/bnv06OGwHRMT4+4SK+tDQibgBllZWca5c+eMH374wZg5c6YRGBho2X/ES3u9+OKLTuvZsGGD4ePjU2pfDw8PY+zYsUZeXp5hGIaRkZFh1KtXz+m+AwcONLKysgybzWZcvHjR+PHHH41hw4aVOPb9999vbNu2zUhNTTVyc3PtdeXk5BjJycnGkiVLSgwc3t7exocffmgcP37cuHTpkuk49+rVy9JjGBoaauzcubPU79dmsxkjRoxwabzhw4cbly9ftvdztk/dunWNYcOGGdu2bauW783Zn1d3HMfExEQjMjKyXONGR0eb/hwANREhs4YjZOJa1aZNG0v/g+3qq3hIudqePXuMO+64w2lIufvuu41///vfpj5vvvmmw7716tUzpk6dahQWFhqGYRjbtm0rd42PPPKIffw6deqUq2+jRo1MNRYWFhpTp0619DjeeuutRk5OTqnfcWFhoTFmzJgSP0NAQIAxb948hz7FQ2aLFi2MadOmGWlpadX6vZX0edxxHPPy8oy33nqrzP8jFhwcbHzwwQdGQUFBqeMBNcX1EDI9DKMGr55chvT0dDVq1MihLTQ01OGan82bN6tnz57VXBlwbSooKNCePXuUkJCgtLQ03XPPPerYsaOCgoJK7LN9+3Z9//33Cg8PV3R0tMLDw6ux4rLl5+dr0KBB+u677+x1njlzRmfPntXZs2d18eJFZWdnKysrS9nZ2fb/ff78efsrOTnZdEPNrFmzNGbMmDLn379/v+Li4nT48GFl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oqOjS5xv586dJu1fQUGBZK6kpCSj52nXrp1m/Oeff270+Pfee08zvnv37kaP13Uk+Nq1a6Jt27ay4dLHx0f897//lYxZtGiRVl8HBwcxf/582deqqNjYWNGxY0ehUqk0Y62srMTUqVN1BmGiqqgqhEyVEK/uFXZTUlLg6uqq1ebi4qL1xf6wsDD06NGjnCsjImPcuXMH+/fvR0xMDOLj45GZmYkmTZqgWbNm8PDwwKBBg2TPXibD5efn49q1a7h69SqSk5PRvn17dO7cWfaEnZfOnTuH33//HfXq1YO/vz/q1atn8HrJyck4efIknJyc4OPjI/ldTVTVjRgxAnv37tVs+/n5aV39ITAwEHv27DFHaUpZa2XuCoiIWrZsiS+++MLcZVRq1tbW6NChAzp06GDwGF9fX/j6+pq0nqurK0aOHGnSWCKqHHh2OREREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcVbmLkBpmZmZWtujRo2Cvb29maohIiIikkpKStLaTk1NNVMlZafShcy8vDyt7cePH5upEiIiIiLDvHjxwtwlKI4flxMRERGR4hgyiYiIiEhxle7jcjs7O2RlZWm2W7ZsCUdHRzNWRERERKTt3r17ePLkiWa7Zs2aZqymbFS6kGlvb68VMr/77jv06NHDfAURERERFTNixAjs3btXs+3s7GzGasoGPy4nIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFGdl7gKIiMpSRkYGdu7ciZSUFMyfP7/M1xNCYP/+/bhz5w7at2xf1nUAACAASURBVG+Pfv36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6623RExMjMjKyip3fkQ1HUNmDceQSXVRZVaH7n9V5OjRoxWG19atW4s9e/bo29wfMh0cHMTUqVNFbGysUZ+nsLBQLFmyRDg4OJQ7plarFStXrjQYWI8ePWryz2LMmDH69hYWFia1dXFxUZxHQkKC6NatmySsW1lZiWnTponMzExZ/ZYtW5o0bo8ePfRtX331VdV+F6oyj+r+brdv316pz2fK6i1RTVMfQqZGiNr7XK+MjAy4uLhIypycnCQXiEdGRsLf3/8Bz4yo5jt+/Dj27t2Ly5cvIy0tDVqtFm3btsXw4cMxcOBAWFlZ6euGhobi/PnzePbZZxEcHKy4LVJFCgoKcODAARw8eBApKSnIyMiAVqtFq1atMGjQIAwZMkTxOsaaKj09Hb/++iscHR3RpUsX2d9F9Uld+26JHoSxY8ciIiJCf+zn54cjR47ojwMDA7Fr1y5zTE0tq60qrkNEddHjjz+Oxx9/3Ki6a9eurfJ4tra2GDlypMG7k2sbFxcXBAUFmXsaNUJd+26JSB28u5yIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDorc09AbUVFRZLjpUuXYuvWrWaaDREREZHcX3/9JTnOyckx00yqT50Lmbm5uZLjH3/80UwzISIiIjLOjRs3zD0F1fF0ORERERGpjiGTiIiIiFRX506XW1lZSa7LtLW1haWlpRlnRERERCRVWFiIkpIS/XGDBg3MOJvqUedCZuPGjZGZmak//umnn+Dv72++CRERERGVMXbsWEREROiPmzdvjn///deMM1IfT5cTERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1D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", + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": { + "image/png": { + "width": 200 + } + }, + "output_type": "execute_result" + } + ], "source": [ "store.write(\"clover_er_diagram_no_schema.png\", \"ER_Diagram\")\n", "\n", @@ -537,10 +1441,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "id": "e1104b9e-a3af-4e5e-a8d6-acae74c59b0e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created an instance of DSI with the Sqlite backend: diana_schema_tutorial.db\n", + "Successfully loaded the schema file: ./clover3d/schema2.json\n", + "Loaded ./clover3d/ into the tables: input, output, simulation, viz_files\n", + "Loaded clover3d/dublin_core_dc.xml into the tables: dublin_core_datacard\n" + ] + }, + { + "ename": "RuntimeError", + "evalue": "ERROR: Cannot call summary() on an empty backend. Please ensure there is data in it.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[25], line 14\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[38;5;66;03m# Read in Datacard (Tier 1)\u001b[39;00m\n\u001b[1;32m 12\u001b[0m schema_store\u001b[38;5;241m.\u001b[39mread(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mclover3d/dublin_core_dc.xml\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mDublinCoreDatacard\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m---> 14\u001b[0m \u001b[43mschema_store\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msummary\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 16\u001b[0m \u001b[38;5;66;03m# dsi.write(filename, writer)\u001b[39;00m\n\u001b[1;32m 17\u001b[0m schema_store\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mclover_er_diagram.png\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mER_Diagram\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "File \u001b[0;32m/Users/Shared/dev/dsi/dsi/dsi.py:1169\u001b[0m, in \u001b[0;36mDSI.summary\u001b[0;34m(self, table_name, collection)\u001b[0m\n\u001b[1;32m 1153\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1154\u001b[0m \u001b[38;5;124;03mPrints numerical metadata and (optionally) sample data from tables in the active backend.\u001b[39;00m\n\u001b[1;32m 1155\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1166\u001b[0m \u001b[38;5;124;03m If False (default), prints each table's name and numerical metadata to the console.\u001b[39;00m\n\u001b[1;32m 1167\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1168\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mt\u001b[38;5;241m.\u001b[39mvalid_backend(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmain_backend_obj, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmain_backend_obj\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__bases__\u001b[39m[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m):\n\u001b[0;32m-> 1169\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mERROR: Cannot call summary() on an empty backend. Please ensure there is data in it.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 1170\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mschema_read:\n\u001b[1;32m 1171\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mERROR: Cannot call summary() until all associated data is loaded after a complex schema\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mRuntimeError\u001b[0m: ERROR: Cannot call summary() on an empty backend. Please ensure there is data in it." + ] + } + ], "source": [ "# Create a new database where we will relate a complex schema\n", "schema_store = DSI(\"diana_schema_tutorial.db\")\n", @@ -553,7 +1480,9 @@ "schema_store.read(\"./clover3d/\", 'Cloverleaf')\n", "\n", "# Read in Datacard (Tier 1)\n", - "schema_store.read(\"clover3d/genesis_datacard.xlsx\", 'GenesisDatacard')\n", + "schema_store.read(\"clover3d/dublin_core_dc.xml\", 'DublinCoreDatacard')\n", + "\n", + "schema_store.summary()\n", "\n", "# dsi.write(filename, writer)\n", "schema_store.write(\"clover_er_diagram.png\", \"ER_Diagram\")" @@ -644,6 +1573,15 @@ "# Moving your data and metadata with DSI" ] }, + { + "cell_type": "markdown", + "id": "eb6d4db2-007c-4d8d-9085-0ac4c02d50c8", + "metadata": {}, + "source": [ + "Once metadata has been ingested, you can use DSI's copy/move capabilities to stage and target local or remote locations.\n", + "On a DSI move, first a local data location and remote location need to be defined. DSI requires an indexing step to for internal book-keeping. DSI will not copy or move until an index is ran." + ] + }, { "cell_type": "code", "execution_count": null, @@ -654,6 +1592,14 @@ "from dsi.sync import Sync" ] }, + { + "cell_type": "markdown", + "id": "b6397dd9-9858-4114-8981-0991c3ad797e", + "metadata": {}, + "source": [ + "Both file paths should be absolute paths, ../../'s are not allowed for example" + ] + }, { "cell_type": "code", "execution_count": null, @@ -667,6 +1613,16 @@ "remote_path = \"/Users/Shared/staging/\"" ] }, + { + "cell_type": "markdown", + "id": "c5ac81e4-2dbc-4e05-a8dc-d09cf99107fb", + "metadata": {}, + "source": [ + "Create a Sync object. DSI expects the 'project name' to be the name of your database file. \n", + "Rename the database file before this step if you'd like to rename the project, before indexing.\n", + "For example, 'dsi-tutorial.db' will be the project name for this example." + ] + }, { "cell_type": "code", "execution_count": null, @@ -678,6 +1634,14 @@ "s = Sync(\"dsi-diana-tutorial\")" ] }, + { + "cell_type": "markdown", + "id": "a9274e80-9b21-4fe9-853f-221049601b95", + "metadata": {}, + "source": [ + "Run the index call, passing the True flag will enable debug mode that prints the progress of the index (filecrawl) and the entry into the database." + ] + }, { "cell_type": "code", "execution_count": null, @@ -688,6 +1652,14 @@ "s.index(local_files,remote_path,True) # These are user defined for now" ] }, + { + "cell_type": "markdown", + "id": "61da8b08-d31c-4f42-b9d3-4e845f7a7f71", + "metadata": {}, + "source": [ + "(Optional) Print out the status of the database. This needs to use the previous store object (not Sync object)." + ] + }, { "cell_type": "code", "execution_count": null, @@ -698,6 +1670,14 @@ "store.summary()" ] }, + { + "cell_type": "markdown", + "id": "cc2c831a-fea8-40a3-abee-f627f8af72aa", + "metadata": {}, + "source": [ + "The user needs to invoke the DSI Copy method. By calling \"copy\", this will initiate a local filesystem Unix/Windows copy to move the data and metadata database to the remote location. DSI will automatically create a new folder at the remote location that is the name of the project, and then attempt to initiate a copy. True will print out the status of the move." + ] + }, { "cell_type": "code", "execution_count": null, From f6bb0bec75bb74f8ff3a571555855ec329971f0c Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Wed, 29 Apr 2026 09:26:33 -0600 Subject: [PATCH 096/109] cleared outputs --- examples/dsi_diana_tutorial.ipynb | 1164 +---------------------------- 1 file changed, 40 insertions(+), 1124 deletions(-) diff --git a/examples/dsi_diana_tutorial.ipynb b/examples/dsi_diana_tutorial.ipynb index 03fe9063..f829d989 100644 --- a/examples/dsi_diana_tutorial.ipynb +++ b/examples/dsi_diana_tutorial.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "0fe6f810-9b2d-4369-9e46-2d78c96e2789", "metadata": {}, "outputs": [], @@ -32,18 +32,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "14e76c45-9449-4fcc-bdbd-6c287a430f37", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Created an instance of DSI\n" - ] - } - ], + "outputs": [], "source": [ "# Create instance of DSI\n", "baseline = DSI()" @@ -74,28 +66,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "14b7ee57-da21-4c6c-86de-bf358907955e", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from IPython.display import HTML\n", "\n", @@ -118,20 +92,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "ffd67bca-2b0a-4eb0-bf56-12d09a2baee3", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Created an instance of DSI with the Sqlite backend: dsi-diana-tutorial.db\n", - "Loaded clover3d/dublin_core_dc.xml into the tables: dublin_core_datacard\n", - "Loaded ./clover3d/ into the tables: input, output, simulation, viz_files\n" - ] - } - ], + "outputs": [], "source": [ "# Target backend defaults to SQLite since not defined\n", "store = DSI(\"dsi-diana-tutorial.db\")\n", @@ -154,18 +118,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "b911a426-a0f2-45a5-94bc-a71fce55f7bd", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Database now has 5 tables\n" - ] - } - ], + "outputs": [], "source": [ "# How many tables do we have\n", "store.num_tables()" @@ -173,39 +129,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "ec76764e-e25e-4720-8cc2-d7eab8d59363", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Table: dublin_core_datacard\n", - " - num of columns: 15\n", - " - num of rows: 1\n", - "\n", - "Table: input\n", - " - num of columns: 22\n", - " - num of rows: 8\n", - "\n", - "Table: output\n", - " - num of columns: 10\n", - " - num of rows: 720\n", - "\n", - "Table: simulation\n", - " - num of columns: 2\n", - " - num of rows: 8\n", - "\n", - "Table: viz_files\n", - " - num of columns: 2\n", - " - num of rows: 80\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# Let's see what tables were created\n", "store.list()" @@ -213,94 +140,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "146d4e66-57df-48f5-ba2e-0c438668310c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Table: dublin_core_datacard\n", - "\n", - "column | type | unique | min | max | avg | std_dev\n", - "-------------------------------------------------------------\n", - "Creator | VARCHAR | 1 | None | None | None | None \n", - "Contributor | VARCHAR | 1 | None | None | None | None \n", - "Publisher | VARCHAR | 1 | None | None | None | None \n", - "Title | VARCHAR | 1 | None | None | None | None \n", - "Date | VARCHAR | 1 | None | None | None | None \n", - "Language | VARCHAR | 1 | None | None | None | None \n", - "Format | VARCHAR | 1 | None | None | None | None \n", - "Subject | VARCHAR | 1 | None | None | None | None \n", - "Description | VARCHAR | 1 | None | None | None | None \n", - "Identifier | VARCHAR | 1 | None | None | None | None \n", - "Relation | VARCHAR | 1 | None | None | None | None \n", - "Source | VARCHAR | 1 | None | None | None | None \n", - "Type | VARCHAR | 1 | None | None | None | None \n", - "Coverage | VARCHAR | 1 | None | None | None | None \n", - "Rights | VARCHAR | 1 | None | None | None | None \n", - "\n", - "Table: input\n", - "\n", - "column | type | unique | min | max | avg | std_dev \n", - "------------------------------------------------------------------------------------------------\n", - "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792 \n", - "state1_density | FLOAT | 1 | 0.2 | 0.2 | 0.19999999999999998 | 2.7755575615628914e-17\n", - "state1_energy | FLOAT | 1 | 1.0 | 1.0 | 1.0 | 0.0 \n", - "state2_density | FLOAT | 8 | 2.0 | 9.0 | 5.5 | 2.29128784747792 \n", - "state2_energy | FLOAT | 1 | 2.5 | 2.5 | 2.5 | 0.0 \n", - "state2_geometry | VARCHAR | 1 | None | None | None | None \n", - "state2_xmin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", - "state2_xmax | FLOAT | 1 | 5.0 | 5.0 | 5.0 | 0.0 \n", - "state2_ymin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", - "state2_ymax | FLOAT | 1 | 2.0 | 2.0 | 2.0 | 0.0 \n", - "x_cells | INTEGER | 1 | 60 | 60 | 60.0 | 0.0 \n", - "y_cells | INTEGER | 1 | 60 | 60 | 60.0 | 0.0 \n", - "xmin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", - "ymin | FLOAT | 1 | 0.0 | 0.0 | 0.0 | 0.0 \n", - "xmax | FLOAT | 1 | 10.0 | 10.0 | 10.0 | 0.0 \n", - "ymax | FLOAT | 1 | 10.0 | 10.0 | 10.0 | 0.0 \n", - "initial_timestep | FLOAT | 1 | 0.04 | 0.04 | 0.04 | 0.0 \n", - "timestep_rise | FLOAT | 1 | 1.5 | 1.5 | 1.5 | 0.0 \n", - "max_timestep | FLOAT | 1 | 0.04 | 0.04 | 0.04 | 0.0 \n", - "end_step | INTEGER | 1 | 90 | 90 | 90.0 | 0.0 \n", - "test_problem | INTEGER | 1 | 2 | 2 | 2.0 | 0.0 \n", - "visit_frequency | INTEGER | 1 | 10 | 10 | 10.0 | 0.0 \n", - "\n", - "Table: output\n", - "\n", - "column | type | unique | min | max | avg | std_dev \n", - "-------------------------------------------------------------------------------------------------------------------------------------------\n", - "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792 \n", - "step | INTEGER | 90 | 1 | 90 | 45.5 | 25.979158313283875 \n", - "time | FLOAT | 90 | 0.0 | 3.56 | 1.7799999999999996 | 1.0391663325313554 \n", - "control | VARCHAR | 1 | None | None | None | None \n", - "timestep | FLOAT | 1 | 0.04 | 0.04 | 0.0399999999999994 | 6.036837696399289e-16\n", - "x | FLOAT | 1 | 0.0833 | 0.0833 | 0.08330000000000087 | 8.743006318923108e-16\n", - "y | FLOAT | 1 | 0.0833 | 0.0833 | 0.08330000000000087 | 8.743006318923108e-16\n", - "wall_clock | FLOAT | 711 | 0.00021386146545410156 | 0.1322460174560547 | 0.054996451073222694 | 0.03471222483372871 \n", - "average_time_per_cell | FLOAT | 706 | 5.9405962626139325e-08 | 3.724901212586297e-07 | 3.012494375437398e-07 | 8.578276515102002e-08\n", - "step_time_per_cell | FLOAT | 211 | 5.7220458984375e-08 | 3.2230880525377062e-06 | 3.3594816737109483e-07 | 8.433991864636084e-07\n", - "\n", - "Table: simulation\n", - "\n", - "column | type | unique | min | max | avg | std_dev \n", - "-----------------------------------------------------------------------\n", - "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792\n", - "sim_datetime | VARCHAR | 2 | None | None | None | None \n", - "\n", - "Table: viz_files\n", - "\n", - "column | type | unique | min | max | avg | std_dev \n", - "-------------------------------------------------------------------------\n", - "sim_id | INTEGER | 8 | 1 | 8 | 4.5 | 2.29128784747792\n", - "image_filepath | VARCHAR | 80 | None | None | None | None \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# Let's get more details about the data\n", "store.summary()" @@ -308,49 +151,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "3e00fca3-3f72-4a4e-bd4c-7c61ec3ed113", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Table: viz_files\n", - "\n", - "sim_id | image_filepath \n", - "-------------------------------------------\n", - "1 | run_1/clover.00000.00001.00000.vtk\n", - "1 | run_1/clover.00000.00001.00010.vtk\n", - "1 | run_1/clover.00000.00001.00020.vtk\n", - "1 | run_1/clover.00000.00001.00030.vtk\n", - "1 | run_1/clover.00000.00001.00040.vtk\n", - "1 | run_1/clover.00000.00001.00050.vtk\n", - "1 | run_1/clover.00000.00001.00060.vtk\n", - "1 | run_1/clover.00000.00001.00070.vtk\n", - "1 | run_1/clover.00000.00001.00080.vtk\n", - "1 | run_1/clover.00000.00001.00090.vtk\n", - "2 | run_2/clover.00000.00001.00000.vtk\n", - "2 | run_2/clover.00000.00001.00010.vtk\n", - "2 | run_2/clover.00000.00001.00020.vtk\n", - "2 | run_2/clover.00000.00001.00030.vtk\n", - "2 | run_2/clover.00000.00001.00040.vtk\n", - "2 | run_2/clover.00000.00001.00050.vtk\n", - "2 | run_2/clover.00000.00001.00060.vtk\n", - "2 | run_2/clover.00000.00001.00070.vtk\n", - "2 | run_2/clover.00000.00001.00080.vtk\n", - "2 | run_2/clover.00000.00001.00090.vtk\n", - "3 | run_3/clover.00000.00001.00000.vtk\n", - "3 | run_3/clover.00000.00001.00010.vtk\n", - "3 | run_3/clover.00000.00001.00020.vtk\n", - "3 | run_3/clover.00000.00001.00030.vtk\n", - "3 | run_3/clover.00000.00001.00040.vtk\n", - " ... showing 25 of 80 rows\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# Preview the contents of the visualization files\n", "store.display(\"viz_files\")" @@ -368,50 +172,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "65ed9155-e447-43ac-b556-72b24e1207ee", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finding all rows where 'wall_clock > 0.10' in the active backend\n", - "\n", - "Table: output\n", - "\n", - "sim_id | step | time | control | timestep | x | y | wall_clock | average_time_per_cell | step_time_per_cell \n", - "-----------------------------------------------------------------------------------------------------------------------------------\n", - "1 | 80 | 3.16 | sound | 0.04 | 0.0833 | 0.0833 | 0.10525012016296387 | 3.6545180612140234e-07 | 3.0330816904703774e-06\n", - "1 | 81 | 3.2 | sound | 0.04 | 0.0833 | 0.0833 | 0.10552811622619629 | 3.618934026961464e-07 | 7.503562503390842e-08 \n", - "1 | 82 | 3.24 | sound | 0.04 | 0.0833 | 0.0833 | 0.10580706596374512 | 3.584250202023886e-07 | 7.556544409857855e-08 \n", - "1 | 83 | 3.28 | sound | 0.04 | 0.0833 | 0.0833 | 0.10608291625976562 | 3.5502984022679257e-07 | 7.410844167073568e-08 \n", - "1 | 84 | 3.32 | sound | 0.04 | 0.0833 | 0.0833 | 0.10635900497436523 | 3.5171628629089033e-07 | 7.443957858615452e-08 \n", - "1 | 85 | 3.36 | sound | 0.04 | 0.0833 | 0.0833 | 0.10663509368896484 | 3.4848069832995046e-07 | 7.503562503390842e-08 \n", - "1 | 86 | 3.4 | sound | 0.04 | 0.0833 | 0.0833 | 0.10691094398498535 | 3.453195865148106e-07 | 7.470448811848959e-08 \n", - "1 | 87 | 3.44 | sound | 0.04 | 0.0833 | 0.0833 | 0.10718393325805664 | 3.422220091253405e-07 | 7.417466905381945e-08 \n", - "1 | 88 | 3.48 | sound | 0.04 | 0.0833 | 0.0833 | 0.10745906829833984 | 3.3920160447708285e-07 | 7.470448811848959e-08 \n", - "1 | 89 | 3.52 | sound | 0.04 | 0.0833 | 0.0833 | 0.10774111747741699 | 3.362706537996785e-07 | 7.66913096110026e-08 \n", - "1 | 90 | 3.56 | sound | 0.04 | 0.0833 | 0.0833 | 0.13019490242004395 | nan | nan \n", - "2 | 80 | 3.16 | sound | 0.04 | 0.0833 | 0.0833 | 0.10534191131591797 | 3.6577052540249293e-07 | 3.0422210693359374e-06\n", - "2 | 81 | 3.2 | sound | 0.04 | 0.0833 | 0.0833 | 0.1056208610534668 | 3.6221145765935115e-07 | 7.549921671549479e-08 \n", - "2 | 82 | 3.24 | sound | 0.04 | 0.0833 | 0.0833 | 0.10589790344238281 | 3.5873273523842416e-07 | 7.523430718315972e-08 \n", - "2 | 83 | 3.28 | sound | 0.04 | 0.0833 | 0.0833 | 0.10617494583129883 | 3.55337837454146e-07 | 7.496939765082466e-08 \n", - "2 | 84 | 3.32 | sound | 0.04 | 0.0833 | 0.0833 | 0.10645294189453125 | 3.520269242544023e-07 | 7.556544409857855e-08 \n", - "2 | 85 | 3.36 | sound | 0.04 | 0.0833 | 0.0833 | 0.10673403739929199 | 3.488040437885359e-07 | 7.642640007866753e-08 \n", - "2 | 86 | 3.4 | sound | 0.04 | 0.0833 | 0.0833 | 0.10701203346252441 | 3.4564610291513055e-07 | 7.556544409857855e-08 \n", - "2 | 87 | 3.44 | sound | 0.04 | 0.0833 | 0.0833 | 0.10729098320007324 | 3.425638033207958e-07 | 7.556544409857855e-08 \n", - "2 | 88 | 3.48 | sound | 0.04 | 0.0833 | 0.0833 | 0.10756993293762207 | 3.3955155598996867e-07 | 7.556544409857855e-08 \n", - "2 | 89 | 3.52 | sound | 0.04 | 0.0833 | 0.0833 | 0.1078500747680664 | 3.3661072024989517e-07 | 7.556544409857855e-08 \n", - "2 | 90 | 3.56 | sound | 0.04 | 0.0833 | 0.0833 | 0.13074803352355957 | nan | nan \n", - "3 | 80 | 3.16 | sound | 0.04 | 0.0833 | 0.0833 | 0.10527896881103516 | 3.655519750383165e-07 | 3.060566054450141e-06 \n", - "3 | 81 | 3.2 | sound | 0.04 | 0.0833 | 0.0833 | 0.10556888580322266 | 3.6203321606043436e-07 | 7.801585727267796e-08 \n", - "3 | 82 | 3.24 | sound | 0.04 | 0.0833 | 0.0833 | 0.1058499813079834 | 3.585703973847676e-07 | 7.60952631632487e-08 \n", - " ... showing 25 of 88 rows\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# Search string or value within all tables\n", "store.find(\"wall_clock > 0.10\")" @@ -419,18 +183,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "266811c7-f79e-4bd4-9ee0-0e40a97fa8ed", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finding all rows where 'state2_density==8.0' in the active backend\n" - ] - } - ], + "outputs": [], "source": [ "# Perform a find and receive a collection\n", "find_list = store.find(\"state2_density==8.0\", True) # Use True to return a collection" @@ -438,105 +194,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "bee1a1b9-a749-4bde-ab1d-abebd386622f", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_idstate1_densitystate1_energystate2_densitystate2_energystate2_geometrystate2_xminstate2_xmaxstate2_yminstate2_ymax...xminyminxmaxymaxinitial_timesteptimestep_risemax_timestepend_steptest_problemvisit_frequency
070.21.08.02.5rectangle0.05.00.02.0...0.00.010.010.00.041.50.0490210
\n", - "

1 rows × 22 columns

\n", - "
" - ], - "text/plain": [ - " sim_id state1_density state1_energy state2_density state2_energy \\\n", - "0 7 0.2 1.0 8.0 2.5 \n", - "\n", - " state2_geometry state2_xmin state2_xmax state2_ymin state2_ymax ... \\\n", - "0 rectangle 0.0 5.0 0.0 2.0 ... \n", - "\n", - " xmin ymin xmax ymax initial_timestep timestep_rise max_timestep \\\n", - "0 0.0 0.0 10.0 10.0 0.04 1.5 0.04 \n", - "\n", - " end_step test_problem visit_frequency \n", - "0 90 2 10 \n", - "\n", - "[1 rows x 22 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Simply display what this collection (pandas dataframe) looks like\n", "find_list" @@ -544,682 +205,40 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "34ab0de4-00bc-4184-b4b5-6e3b4352367f", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finding all rows where 'time>3.0' in the active backend\n" - ] - } - ], + "outputs": [], "source": [ "find_list = store.find(\"time>3.0\", True)" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "d108854d-6f28-4adf-bcae-3de04c408fc7", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_idsteptimecontroltimestepxywall_clockaverage_time_per_cellstep_time_per_cell
01773.04sound0.040.08330.08330.0937793.383084e-077.417467e-08
11783.08sound0.040.08330.08330.0940513.349399e-077.390976e-08
21793.12sound0.040.08330.08330.0943243.316600e-077.417467e-08
31803.16sound0.040.08330.08330.1052503.654518e-073.033082e-06
41813.20sound0.040.08330.08330.1055283.618934e-077.503563e-08
.................................
1078863.40sound0.040.08330.08330.1078073.482136e-077.530053e-08
1088873.44sound0.040.08330.08330.1080863.451018e-077.556544e-08
1098883.48sound0.040.08330.08330.1083643.420577e-077.523431e-08
1108893.52sound0.040.08330.08330.1086433.390857e-077.556544e-08
1118903.56sound0.040.08330.08330.130823NaNNaN
\n", - "

112 rows × 10 columns

\n", - "
" - ], - "text/plain": [ - " sim_id step time control timestep x y wall_clock \\\n", - "0 1 77 3.04 sound 0.04 0.0833 0.0833 0.093779 \n", - "1 1 78 3.08 sound 0.04 0.0833 0.0833 0.094051 \n", - "2 1 79 3.12 sound 0.04 0.0833 0.0833 0.094324 \n", - "3 1 80 3.16 sound 0.04 0.0833 0.0833 0.105250 \n", - "4 1 81 3.20 sound 0.04 0.0833 0.0833 0.105528 \n", - ".. ... ... ... ... ... ... ... ... \n", - "107 8 86 3.40 sound 0.04 0.0833 0.0833 0.107807 \n", - "108 8 87 3.44 sound 0.04 0.0833 0.0833 0.108086 \n", - "109 8 88 3.48 sound 0.04 0.0833 0.0833 0.108364 \n", - "110 8 89 3.52 sound 0.04 0.0833 0.0833 0.108643 \n", - "111 8 90 3.56 sound 0.04 0.0833 0.0833 0.130823 \n", - "\n", - " average_time_per_cell step_time_per_cell \n", - "0 3.383084e-07 7.417467e-08 \n", - "1 3.349399e-07 7.390976e-08 \n", - "2 3.316600e-07 7.417467e-08 \n", - "3 3.654518e-07 3.033082e-06 \n", - "4 3.618934e-07 7.503563e-08 \n", - ".. ... ... \n", - "107 3.482136e-07 7.530053e-08 \n", - "108 3.451018e-07 7.556544e-08 \n", - "109 3.420577e-07 7.523431e-08 \n", - "110 3.390857e-07 7.556544e-08 \n", - "111 NaN NaN \n", - "\n", - "[112 rows x 10 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "find_list" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "38fb33ad-a6d8-489c-8195-f2d8f4cad785", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finding all rows where 'time(1.0,1.1)' in the active backend\n" - ] - } - ], + "outputs": [], "source": [ "find_list = store.find(\"time(1.0,1.1)\", True)" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "5c167011-fd2c-4b1a-bf62-9e16b4017d20", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_idsteptimecontroltimestepxywall_clockaverage_time_per_cellstep_time_per_cell
01261.00sound0.040.08330.08330.0272032.906312e-076.947252e-08
11271.04sound0.040.08330.08330.0274602.825113e-076.973743e-08
21281.08sound0.040.08330.08330.0277372.751677e-077.496940e-08
32261.00sound0.040.08330.08330.0272342.909624e-076.781684e-08
42271.04sound0.040.08330.08330.0274842.827566e-076.748570e-08
52281.08sound0.040.08330.08330.0277362.751582e-076.801552e-08
63261.00sound0.040.08330.08330.0272602.912400e-076.755193e-08
73271.04sound0.040.08330.08330.0275042.829626e-076.609493e-08
83281.08sound0.040.08330.08330.0277482.752765e-076.583002e-08
94261.00sound0.040.08330.08330.0271562.901269e-076.688966e-08
104271.04sound0.040.08330.08330.0274052.819447e-076.722079e-08
114281.08sound0.040.08330.08330.0276612.744155e-076.920762e-08
125261.00sound0.040.08330.08330.0270562.890596e-076.662475e-08
135271.04sound0.040.08330.08330.0273012.808752e-076.609493e-08
145281.08sound0.040.08330.08330.0275472.732849e-076.642607e-08
156261.00sound0.040.08330.08330.0270542.890367e-076.609493e-08
166271.04sound0.040.08330.08330.0273042.809047e-076.748570e-08
176281.08sound0.040.08330.08330.0275552.733630e-076.808175e-08
187261.00sound0.040.08330.08330.0278282.973075e-076.834666e-08
197271.04sound0.040.08330.08330.0280772.888593e-076.755193e-08
207281.08sound0.040.08330.08330.0283292.810430e-076.808175e-08
218261.00sound0.040.08330.08330.0270102.885680e-076.609493e-08
228271.04sound0.040.08330.08330.0272562.804116e-076.669097e-08
238281.08sound0.040.08330.08330.0275132.729467e-076.947252e-08
\n", - "
" - ], - "text/plain": [ - " sim_id step time control timestep x y wall_clock \\\n", - "0 1 26 1.00 sound 0.04 0.0833 0.0833 0.027203 \n", - "1 1 27 1.04 sound 0.04 0.0833 0.0833 0.027460 \n", - "2 1 28 1.08 sound 0.04 0.0833 0.0833 0.027737 \n", - "3 2 26 1.00 sound 0.04 0.0833 0.0833 0.027234 \n", - "4 2 27 1.04 sound 0.04 0.0833 0.0833 0.027484 \n", - "5 2 28 1.08 sound 0.04 0.0833 0.0833 0.027736 \n", - "6 3 26 1.00 sound 0.04 0.0833 0.0833 0.027260 \n", - "7 3 27 1.04 sound 0.04 0.0833 0.0833 0.027504 \n", - "8 3 28 1.08 sound 0.04 0.0833 0.0833 0.027748 \n", - "9 4 26 1.00 sound 0.04 0.0833 0.0833 0.027156 \n", - "10 4 27 1.04 sound 0.04 0.0833 0.0833 0.027405 \n", - "11 4 28 1.08 sound 0.04 0.0833 0.0833 0.027661 \n", - "12 5 26 1.00 sound 0.04 0.0833 0.0833 0.027056 \n", - "13 5 27 1.04 sound 0.04 0.0833 0.0833 0.027301 \n", - "14 5 28 1.08 sound 0.04 0.0833 0.0833 0.027547 \n", - "15 6 26 1.00 sound 0.04 0.0833 0.0833 0.027054 \n", - "16 6 27 1.04 sound 0.04 0.0833 0.0833 0.027304 \n", - "17 6 28 1.08 sound 0.04 0.0833 0.0833 0.027555 \n", - "18 7 26 1.00 sound 0.04 0.0833 0.0833 0.027828 \n", - "19 7 27 1.04 sound 0.04 0.0833 0.0833 0.028077 \n", - "20 7 28 1.08 sound 0.04 0.0833 0.0833 0.028329 \n", - "21 8 26 1.00 sound 0.04 0.0833 0.0833 0.027010 \n", - "22 8 27 1.04 sound 0.04 0.0833 0.0833 0.027256 \n", - "23 8 28 1.08 sound 0.04 0.0833 0.0833 0.027513 \n", - "\n", - " average_time_per_cell step_time_per_cell \n", - "0 2.906312e-07 6.947252e-08 \n", - "1 2.825113e-07 6.973743e-08 \n", - "2 2.751677e-07 7.496940e-08 \n", - "3 2.909624e-07 6.781684e-08 \n", - "4 2.827566e-07 6.748570e-08 \n", - "5 2.751582e-07 6.801552e-08 \n", - "6 2.912400e-07 6.755193e-08 \n", - "7 2.829626e-07 6.609493e-08 \n", - "8 2.752765e-07 6.583002e-08 \n", - "9 2.901269e-07 6.688966e-08 \n", - "10 2.819447e-07 6.722079e-08 \n", - "11 2.744155e-07 6.920762e-08 \n", - "12 2.890596e-07 6.662475e-08 \n", - "13 2.808752e-07 6.609493e-08 \n", - "14 2.732849e-07 6.642607e-08 \n", - "15 2.890367e-07 6.609493e-08 \n", - "16 2.809047e-07 6.748570e-08 \n", - "17 2.733630e-07 6.808175e-08 \n", - "18 2.973075e-07 6.834666e-08 \n", - "19 2.888593e-07 6.755193e-08 \n", - "20 2.810430e-07 6.808175e-08 \n", - "21 2.885680e-07 6.609493e-08 \n", - "22 2.804116e-07 6.669097e-08 \n", - "23 2.729467e-07 6.947252e-08 " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "find_list" ] @@ -1236,30 +255,10 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "8fd1e99e-d293-46d2-b6e8-b353cf027f07", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Printing the result of the query: SELECT sim_id, xmin, ymin, xmax, ymax, state2_density FROM input\n", - "\n", - "sim_id | xmin | ymin | xmax | ymax | state2_density\n", - "---------------------------------------------------\n", - "1.0 | 0.0 | 0.0 | 10.0 | 10.0 | 2.0 \n", - "2.0 | 0.0 | 0.0 | 10.0 | 10.0 | 3.0 \n", - "3.0 | 0.0 | 0.0 | 10.0 | 10.0 | 4.0 \n", - "4.0 | 0.0 | 0.0 | 10.0 | 10.0 | 5.0 \n", - "5.0 | 0.0 | 0.0 | 10.0 | 10.0 | 6.0 \n", - "6.0 | 0.0 | 0.0 | 10.0 | 10.0 | 7.0 \n", - "7.0 | 0.0 | 0.0 | 10.0 | 10.0 | 8.0 \n", - "8.0 | 0.0 | 0.0 | 10.0 | 10.0 | 9.0 \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# Use sql statement to directly query the backend store\n", "store.query(\"SELECT sim_id, xmin, ymin, xmax, ymax, state2_density FROM input\") # Adding 'True' gives a collection" @@ -1267,57 +266,20 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "3f3d15ae-7c92-46b7-ac29-f09b495a7a75", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Table: dublin_core_datacard\n", - " - num of columns: 15\n", - " - num of rows: 1\n", - "\n", - "Table: input\n", - " - num of columns: 22\n", - " - num of rows: 8\n", - "\n", - "Table: output\n", - " - num of columns: 10\n", - " - num of rows: 720\n", - "\n", - "Table: simulation\n", - " - num of columns: 2\n", - " - num of rows: 8\n", - "\n", - "Table: viz_files\n", - " - num of columns: 2\n", - " - num of rows: 80\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "store.list()" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "863d168c-b73a-424e-bf58-d13980a45da1", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "WARNING: 'genesis_datacard' does not exist in this database\n" - ] - } - ], + "outputs": [], "source": [ "# alternative to \"query()\" if you want to get a whole table\n", "store.get_table(\"genesis_datacard\", True) # Adding 'True' gives a collection" @@ -1396,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "d790e4e0", "metadata": {}, "outputs": [], @@ -1406,33 +368,10 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "18d62564-30f0-45af-b664-30f1862b32d9", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully wrote to the output file clover_er_diagram_no_schema.png\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAApkAABanCAYAAABeTu5SAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd1gU1+I+8JcqIKCIEKzYe0UUxaBYsERjSaJXotEYE4NR1JjEdmO8V78paiwxBnsSEWMSvSYaYwzGktiwoRjFFguCNEVEitQ9vz982J/LzBaW2V3K+3mefR7nzDkzZ2d22dcpZ6yEEAJERERERMr5ytrSPSAiIiKiyochk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHEMmURERESkOIZMIiIiIlIcQyYRERERKY4hk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHEMmURERESkOIZMIiIiIlIcQyYRERERKY4hk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHEMmURERESkOIZMIiIiIlIcQyYRERERKY4hk4iIiIgUx5BJRERERIpjyCQiIiIixTFkEhEREZHiGDKJiIiISHG2lu4AVVyhoaH4+++/Ld0NIiIykQULFqBfv36W7gZVUAyZZLTo6GicOHHC0t0gIiITmTx5sqW7QBUYT5cTERERkeIYMomIiIhIcTxdTorp2bMnbGxsLN0NIiIy0o0bN5CUlGTpblAlwZBJirG2tsZff/1l6W4QEZGRWrRowZBJiuHpciIiIiJSHEMmERERESmOp8vJZDp06IDnn3/e0t0gIiIt1q1bB5VKZeluUCXFkEkm06dPH6xatcrS3SAiIi02btzIkEkmw9PlRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBRna+kOEFHFcu/ePRw8eBB3795FmzZtEBQUBBcXF0t3q9wqKCjArVu3cP36dVy/fh02NjaoX78+6tevjy5dusDOzk6jPrcvEVUWDJlEZLBvv/0WoaGhyMrKUpc1atQI27Ztg7+/vwV7Vr4IIbBlyxYsWbIEN27cQFFRkWy9+vXrY8aM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IiKAIZOoypM7Innp0iX0798fL774otbxF62srAx+2osxT4Up65NkSt4hXlpyRwUNuZTAkkq7zfbv3y8J3o0bN1ayS0RUhTFkElVxc+fOhZubm+y8vXv3okuXLhg6dCiioqKMXoeuIY+UbFPVlXabHTlyRFLGkElESmHIJKri3N3d8ddff6F+/fpa6/z666/o0aMHgoKCcI0Dse8AACAASURBVOHCBTP2jkzp9OnTkjJvb28L9ISIKiOGTCJCu3btEBUVhd69e+us98cff8DX1xfTp09HRkaGmXpHppKamiopy87OtkBPiKgyYsgkIgBAvXr1cOTIEezcuVPnKdOioiJ8+eWX8PPzw71798zYQ1JSQUGB7H8U7ty5Y/7OEFGlxJBJRBpefvllXLlyBZ999pnOu4avXbuG3r17IzEx0Yy9I6VYW1vD3t5eUh4XF2eB3hBRZcSQSUQS1apVw5w5c3Dz5k28++67smEEAG7evIk5c+aYuXekBBsbGzRr1kxSziOZRKQUhkwi0srd3R0rVqzAtWvX0K9fP9k627Ztw99//23mnpES5B5FqUTIFEKUeRlEVPExZBJVYbm5uahbty4ePXqks16jRo0QGRmJTz75RDJMjhACf/75pym7SSYiFzL/+usv3L17t0zLLc/PeCci82HIJKrChBBISkrC7t279da1trbGvHnzMHfuXMm8K1eumKJ7ZGJyITM/Px8ff/xxmZZb3getJyLzYMgkIoMfDwkACxcuRJ06dTTKatasqXSXyAz69+8PZ2dnSfk333xTphuACgsLS/WceCKqnBgyiQgHDhxAUlKSQXXt7Ozg6+urUdatWzedbUoe2TLkSJdcSClNcJE7ZVuaZ5fLtS/vR+hKu53r1KmDDz/8UHY58+bNK9O1lRxHlYgYMokI+fn5GDdunMHX0pU8ctm1a1ed9bOysjSmDRnwu2QbQ9sVkwtYT548Mbi9XKAt7wOVG7Od3333Xdm7zLdv347g4GDk5ubqbP/jjz/izJkzkvKyPIaUiCoHhkwiAgAcOnQIixYt0ltPCKERIIKDg1G3bl2t9VNTU5GXlycp03eULT4+XlKWnJyst3/FHj58KCl78OCBwe3lboYqz4PPG7ud7e3tsXLlStl5P/zwA/r374+UlBTJvKKiInzwwQf417/+JRtmFy1ahHv37uHhw4fYu3cv8vPzS/FuiKgyYMgkIrXFixdj1qxZOo+ALV26FDdu3AAAdOzYEZs2bdK5zO3bt0vK8vLysGvXLp3ttm3bJimLiooyKOjl5ubiiy++kJT//vvvyMnJ0ds+Pj5etn8//fRTuR2ex9jtDABDhw7F9OnTZecdP34c9erVQ7du3TBv3jyEhYUhODgY9erVw+eff44BAwZILp8Anj4XvWHDhqhduzbefvttDvJOVBUJIiP5+/sLAOpXQECAxvSMGTMs3UXSIycnR2OfFb8aNWokvv76a3H58mVRWFgoVCqV+Oeff8SkSZOElZWVACCaNWsmbt++LbvcrKwscffuXbFhwwZhZ2cnuw4nJycRHh4u4uPjRXZ2thBCiEePHonY2FgRGhoq2waAaNu2rThw4IBISkoSOTk56nXm5eWJxMREcezYMTFixAit7QMCAsSBAwdEYmKiyM3N1Wh/5swZsWzZMtGhQwet7SdMmCCio6NFWlqaSfeNIYzdztp89NFH6v1ryGvcuHEiPz9fBAYGys7v06eP2LFjhygoKDDTFqHSKvm5adGihcb0tm3bLN1FqrjWMGSS0RgyK77ikFmvXj3x/PPPCxcXF0lQcHR0FE5OTurpGjVqiGXLlom8vDyty61Xr57BQQWA8PPzEykpKaVqA0B07NhRvc7Zs2eXun1wcLC6fa9evUrdPi4uzqT7Rx9jtrM+x44dE+3bt9e5nAYNGohdu3ap2zwbMl1dXcW0adNEbGysKd86KYQhk0xojS2IqMqyt7fHhg0b8NJLL8Hd3R0qlQpXrlzByZMncf78eSQnJ+P+/fuoUaMG2rVrh3bt2mHAgAHw8PDQudyEhASj+iPKcCp6yZIlWLJkidHtK+KA8sZuZ1169uyJixcvIioqCj/99BP++ecfpKSkwM3NDY0aNcLQoUPRr18/2Nr+/5+Pli1bQqVSITg4GOPGjZMdFomIqh6GTKIqzMbGBm+99ZZ62traGm3btkXbtm0t2CsqD7p3747u3bsbVHfdunUm7g0RVUS88YeIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOg7ETEZXB5s2bERYWZvb19u3bF8uWLTP7eomIDMWQSURUBsnJyYiOjjb7er29vc2+TiKi0mDIJCIqAx8fH0yZMsXs6+3UqZPZ10lEVBoMmUREZTB48GAMHjzY0t0gIip3eOMPERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOT/whxRQVFWlMp6en4+rVqxbqDREREVkSQyYp5tatWxrT4eHhCA8Pt1BviIiotPLy8izdBapEeLqciIiIiBTHkElEREREimPIJCIiIiLF8ZpMUkyTJk2QnJysnh4/fjzmzZtnwR4REZEuHTp0QEFBgXq6WrVqFuwNVTYMmaQYGxsbjWk3Nze0atXKQr0hIiIiS+LpciIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxtpbuAJE+e/fuxdWrV+Hj44O+fftaujtEpZKWloZ//vkHt27dQk5ODlq3bo22bduiRo0akrpKfNYTExOxfft2uLi4YPLkyWXtPpnYiRMncODAAfTp0we9evWydHeIFMWQSeXaqFGjsHPnTvX0zJkzsXLlSgv2iMgwv//+O5YtW4aDBw/Kzh8wYACWLVuGDh06ACjbZ10IgYMHD2Lt2rXYs2cPCgsL4evry5BZTmVmZiIiIgLr1q3DxYsXAQA2NjYMmVTp8HQ5lVsHDx7U+NEFgFWrVuHatWsW6hGRfgkJCfDz88OgQYNw5MgRBAYGolevXrCzs9OoFxkZic6dOyMkJASRkZFGfdYfPnyIFStWoGXLlggKCsKuXbtQWFio+HsiZcTExGDKlCmoW7cu3nnnHXXAJKqsGDKp3Lp161apyoks7fz58/Dz88Pp06cRHByMlJQUHD58GH/++SfS0tIwduxYjfoqlQrr16/HwIEDZZen7bP+8OFDvP7666hXrx7ee+893LhxQ/H3Qso5dOgQ/P390alTJ6xbtw5ZWVmW7hKRWTBkUrnVo0cPWFtrfkQdHR3RuXNnC/WISLu7d++id+/eSExMRK1atfDll1/C3d1dPd/FxQXh4eEYPHiwQcvT9VmvUaMG3NzcsG7dOmzduhVNmzZV5D2Qadja2qJ///745ZdfsHjxYlhZWVm6S0RmwWsyqdxq164dVq5ciffffx8FBQVwcnLChg0b4OXlZemuEUm8/fbbyMzMBADMnTtXI2AWs7a2RkREBHx8fBAXF6cx75VXXsHu3bsN+qzb2NhoXK9pbW0tOUpK5UevXr3U11sOHToUZ8+exe7duy3cKyLT45FMKtemT5+Oe/fu4fjx44iPj+cPKZVLP/74I/bv36+e9vX11Vq3Vq1a2LFjB1xdXdVl7dq1w48//mj0Z33QoEHGdZwUERYWpvUGLzlDhw41YW+Iyg8eyaRyz8PDAx4eHpbuBpFWW7Zs0Zh+7rnndNbv2rUrTp8+jfXr18PV1RWhoaGwsrIy+rPu5uYGKysrCCFK3ZbKJjs7G//+97+xZMkS9OvXz6A2cke5iSojhkwiojLIzMyUHMWSGwOzpJYtW2LFihWK9MHKygrVqlVDbm6uIssjw4WHh+PRo0elauPk5GSi3hCVLzxdTkRUBn/88Qfy8vI0yhwdHc3ej5JDJJHp5efnY8mSJaVux31FVQVDJhFRGdy5c0dSVq1aNbP3o+RIDGR6S5YskdzAZQgbGxsT9Iao/OFfJSKiMkhJSZGUWSJEcFgc89qzZw8WLlxoVFvuK6oqGDKJiMogNTVVUmaJo4oMLuZz6dIljB07ljdaEenBkElEVAYZGRmSMga+yuv+/fsYNmwYn9pDZADeXU7lXkZGBsLDwxEeHg5PT0/8+uuvBrWLi4vDxo0bsWvXLgQHB2PBggWSOjdu3MD+/ftx9uxZXLx4EZ6enmjdujUmTpyIjh076lx+YWEh9u7di927d+PWrVtIT0+Ht7c3evTogZCQENSqVcuo9/usP/74A9HR0bhz5w5u376N+Ph4uLi4wMvLC3Xq1EFQUBAGDx4MBweHMq1HpVIhKioKly5dwuXLl3H58mXcv38f3t7eaNKkCZo3b47Ro0crNpRUQUEBjhw5gj179uDatWtITk6GSqVC+/bt0aFDB3To0AHPP/+8QXdpm1N6ejry8/M1yuTu6E5NTYWtrfTPq729Pdzc3LQu39jPupJMtW+uXbuGDRs2IDo6Gnv37kX16tU15l++fBlff/01YmNjkZycjI4dO8LPzw/dunWDj4+P0cFdye/QhQsX8NJLL+H27duSeY8fP5a9dMLZ2VnyXg0lhMCBAwfw/fff4/r168jOzkbz5s0RGBiISZMmWeTaX6JSEURG8vf3FwDUr4CAAI3pGTNmlGn5p0+fFm+88YZwcnJSL7N9+/Y62xQVFYlffvlFDBkyRFhbW6vbhYaGatTLysoSs2fPFnZ2dhp9Ln5ZW1uLiRMniry8PNn17N69W9SpU0e2LQBRvXp1sWPHDqPf+++//y66deumscxatWoJW1tbybpcXV3F3LlzRUFBgVHrunjxoujevbsAIOzs7ET37t3FtGnTxMsvv6zxHh0dHUVISIi4du2ayMnJkbyePHkiVCqVznUVFRWJDRs2CE9PT63brvhVp04dsXfvXqPek6kEBgbq7beuV9++fWWXa8xnvSQ3NzeNdfn6+paqvSn2TV5envj+++9Fnz59NNpnZGSo66SlpYlRo0YJKysrret78cUXRXp6eqnej5LfofT0dDFnzhzh4OBQ6n3+3//+V7K8P//8U1Jv8eLFGnUuXrwo2rZtq3W5DRs2FOfPny/VNpFT8m9gixYtNKa3bdtW5nVQlbWGIZOMZoqQmZWVJTZs2CB8fHxk/7Bq++FNTEwUixcvFg0bNpRt92zIPHnypGjQoIFBPxDjx4/XWE9eXp6YPn26QW1tbGzETz/9VKr3X1RUJP71r3+pl2Fvby8WLlwo0tLS1Ou/cOGCGDJkiGR9/fr1E4WFhQav68mTJ2Lu3LnqH90GDRqI06dPS/rz/vvvG/yD6u7urvWHOjk5Wf2jb2NjI9566y0RHh4uLly4IA4cOCA++OAD4eLiIlnmxIkTNUKJJQ0cOFA4OTlpvGxsbCR9Llmn+DV48GD1soz9rGtTlpCp9L65efOmmDNnjtbAWtzm3r17OoPUs6+mTZuKmJgYve/FFN+hTZs2CS8vL/Wr5LYuDqrP1il+LV++XLI8fSFz48aNBgXaOnXqiLi4OIP3sxyGTDIhhkwyntIh886dO8LV1VVYW1trHIXU98O7e/duYWtrq/Wo5LMh89ChQ6J69eoCgPDz8xPTpk0TH3zwgejVq5eoVq2abNvw8HAhhBAFBQXipZdeUpe7uLiIQYMGibFjx4rmzZvLtq1fv7548uSJwdtg5syZGu3Xrl2rte5bb70lWd+qVasMXtfYsWPV7RwcHMTdu3e11l2wYIHBQVPuiNM///wjmjRpIgCI5s2bi6ioKNn13Lx5U3h4eEiWGRQUZPD7MrdnPxPFr/z8fJ1tjP2s62JsyFRy36hUKjF06FCdRyWLQ2ZaWpp6vYa+nJyc9AZNc3yHfvnlF0m79evX621XTFfIXL58ubrM2dlZDBo0SAQFBWn9+/Tqq68avF45DJlkQgyZZDxTHMk8fPiwyMjIEPfv3xeDBw826Ic3IyNDnDhxQuTm5oqbN2/KHqUMDQ0Vf/zxh3B0dBS9e/eWPc107tw52SMvzz//vCgqKlKHsmHDholTp05Jjtjt379fODo6StqvWLHCoPe+Z88ejXbOzs46j0zeuXNHchStYcOGBq0rIiJCo9306dN11s/MzBS1atXSaGNvby/mzJkj5s2bJ+bNmyfmz58vvvzyS0nbnJwc0aZNGwFAuLm5icTERJ3rOnbsmOwPakREhEHvzdyMCZlCGPdZ18WYkGmKfXPgwAFx8+ZNcfPmTRESEqI1ZI4aNUoAEDVr1hTDhw8Xq1atEhERESIkJEQ0btxYa9Bs3769yM3Nle2fub5DpgqZK1asEMDT4H78+HGNvzFZWVlizpw5knZ2dnZ695suDJlkQgyZZDxTX5N54sQJo354ly5dKmnn6+sr3NzcxMaNG3VeN3jhwgXZH7aRI0cKR0dHvX9wd+7cKWmr7Tq8kt58803JH3t9Bg4cKFlfSkqKzja3b98Wrq6uGm2uXr2qd12LFi2SrOv777/X227y5Mnq+lu3btVbXwghwsLCJOvy9PQUDx8+NKi9ORkbMp9l7Gf9WcaETFPvm/z8fNlrl4uXMWrUKNnT7Tk5OeoQKvf69NNPZftmru+QKUJmmzZthLW1tc4jqYWFhZK/uwDEmjVrDF53SQyZZEJrOIQRlVutWrUyql3r1q0lZVeuXMHOnTvx5ptv6rxLtWPHjvD395eUHz9+HEeOHMGrr76qc93Dhw+X3FV+5swZqFQqvf0+evSoxnRBQYHeNt7e3pKy+Ph4nW3Wr1+Px48fq6etrKzQuHFjveuaNm2apOw///mPzvcWGxuLDRs2AHi6X8aNG6d3PQAwZswYyViTqamp+Pzzzw1qX9EY+1kvC3PsGzs7O9n39v7772P69On48ccf4erqKpnv6OiIH374ASEhIbJ9WLNmjez3w1zfIVO4fv06wsPDMWPGDK11bGxssGzZMkl5bGysKbtGZDSGTCq33NzcjHrG73PPPScpmzhxIvr27WtQ+wEDBkjKPvvsM3Tr1k1vW1tbW/j5+WmUZWZmaoQ6bTp37qwxHRAQoLeNl5eXpCwnJ0dnm3PnzkmWYW9vr3ddbm5ukm179epV3L17V2ublStXqv/du3dvvet4dl0NGjSQlEdHRxu8jIrE2M96WZhr38gN3zNx4kR88cUXOtdjZWWFTz75RDaE3rt3D3v27JGUm+s7ZAqzZs3C2LFj9dZr3769pOzq1aum6BJRmTFkUrlmzDhwcuPdlWaMPU9PT0lZYWGhwe3d3d0lZZmZmXrbvf7663B0dATw9D2MHz9ebxu5QJ2dna2zzdmzZzWmdY3bWJLcUZ+bN2/K1n3y5AkiIiLU07169TJ4PQBQt25dSdmVK1dKtYyKxJxjHppz38i9r08++cSg9bi5uWHmzJmy8/744w9Jmbm+Q6bg4uJicL2S49U+fPjQFF0iKjMOxk7lmjFHd8p6REguZBYVFRncXm4QdkNC5sCBAxEfH4/IyEgMGjRIb/gTQsg+0rDkYOHPunfvHtLT0zXKEhIS9PatmLe3N06fPq1RJjcwNfD0MoFnByp/5513dJ4KLEnuSTp3795Fdna20YNbl2fmPJJpzn1T1qcfvfvuu/jiiy8k6zx+/Likrjm+Q+VBcZAu9uTJEwv1hEg3hkwq1+SemmKKNs8q+Qe8tOSO3BhybRjw9ChocHCwzjpxcXHYsmULtmzZglu3bpWqb3IB+PHjx3j06BFq1qypt73cUZ969erJ1i0ZApo1a6bIE3xSU1MNuoa0oinr57Y0KtK+qVmzJiZMmIDVq1drlF++fBlCCEmINfV3qDwoebZG7qlTROUBQyaVazY2NmZpU96pVCr89ttvWLt2LX777TeoVCrUr18fffv2xaFDhwxejqOjI7y8vJCcnKxRHhcXZ1DIlDvq4+vrK1v38uXLGtMffvghhg8fbnBfqxpzfm4r2r5p2bKlpEylUuHRo0cGX+6h1HeoPGDIpIqC12QSlWO5ublYt24dWrZsiaFDh+LXX39Fv379sGvXLty5c0fvERs5AwcOlJSVvBlIm5I3+Xh7e2t9nnnJ68Qq4hGjyqqi7ZtGjRrJlhtyLaIpvkOWVvI/JKW5ZpzInBgyicqhoqIibNiwAU2bNsWUKVNw8+ZNjBkzBjExMYiMjMTIkSONPvIldyPFsmXL9A6zVFBQgBs3bmiUTZ48WWv9ktd+lvcgU5VUtH0jd8MZoPt6T1N+h4jIMAyZROVMXFwcAgMD8fbbbyMxMRHNmjXDkSNHsH37dnTo0KHMy+/UqZPkrturV69i586dOtt98803SEtLU0/37dsXc+fO1Vq/5M0I5T3IVCUVbd9oO5IpN5IDYPrvEBEZhiGTqBzZs2cPOnbsiGPHjgEAAgMDceHChVIPMaPPhg0b0KNHD42y+fPn486dO7L1U1JS8PHHH6unPTw8EBERIRmU+1klr5XTNtQRmV9F2zfVq1eX9NnW1lZ2DE1zfYeISD+GTKJy4vjx4xg9erR6qJY2bdrgl19+MclwPdWqVcO+ffswYcIEddnNmzfh5+eHX375RT0YdV5eHn777Td07txZfT3m8OHDcfr0adSpU0fnOkreyX7t2jWcOnWqTP0WQpSpPT1V0fZNUVGRZBgwb29vyelyc36HiEg/hkyicuD+/fsYMWIE8vLy1GWLFy+Gs7OzydZZs2ZNfPvtt5g/f766LDU1FcOGDUONGjXQokULuLi44IUXXkBSUhJatmyJ3377DT///LPW05fPkruObvbs2Ub3Nzc3F507d0ZSUpLRy6CnKtq+uXfvnuTmlpJHJi3xHSIi3RgyicqBDRs24MGDB+ppGxsbDBkyxKhlleaI0vfff4/PP/8cI0eORHJyMk6dOoWIiAj8+9//xogRIxAWFoajR48iNTUVV69exaBBgwxe9vPPPy8p++uvv7B3716Dl/GsTZs2ISkpSXasTiodS+8bQ8eNLSZ3GUfJR2Fa6jtERNpxnEyicmD//v0a0x4eHkY/ZtDQpxOFhYVh2rRp8PX1xQ8//AA7Ozs899xzBj2j3RABAQGwsrKS/GDPmzcPgwYNKtXg47m5uViyZAmGDRum8zpQMoyl901pn6gTFxenMe3g4IDBgwdrlFniO0REuvGvNVE5EB8frzGdmppq9KPiDPmB3L17N6ZNmwYhBObNm2eSRxp6eHjIHkm6dOkSxowZY/DRLJVKhXHjxiEhIQEjRoxQuptlJjdGYXl/LKGl901pnw1echzX8ePHSx7/as7vkFyYlnvUJlFVx5BJ5VrJHztDfvzkfvRLM1ix3HiRpTmyIddeX79L3jmrUqnw559/6myTm5uLI0eOSMpL/oA/evRIYzolJQXjxo1TH8XSNgahEt5//33Z8v/9738YPHiwZHD3kvLz8xESEoL//e9/aNWqFYKCgkzRzTIpvknqWYYMEl6SMZ91XfX1feYtuW/0LftZDx48wKZNm9TTNjY2eO+99yT1zPkdkrurXe5pWAAQGxsrWZ7cvinN35iSR6BL+1khMheGTCrXsrKyNKYNOQJSso2h7YrJ/cEuzRERuR8Qfetv3bq1pGzWrFlaj4idPn0aPj4+2LZtm2Tes0d0zp8/LxkTMywsTGMbTZo0Cbt370ZiYqLiTw7p3bu31scVHjx4EG3btsXs2bNx8eJFjR/Oe/fuYePGjWjevDk2btwIW1tbhIeHw97eXtH+KaFkAAGkg50bwpjPejEhhKS+3PfgWZbcN3v27DG47vLlyzXe2/z589GiRQtJPXN+h+QewXrgwAFJWUREBPr06SO5C15u35bmb0zJQFraI8NE5sKQSeVWamqqxp2ixWX6/tde8rQZAMmzunWROwr17A0F+siFjnv37uls07VrV0nZlStX0Lt3b/VNDyqVCqdPn8bMmTPh7++PxMREjB07VtJuy5YtuHTpEiIiIhAYGIg2bdpozI+JidGYvnDhAkaMGIF69erBzs4Ojo6OqFWrFurWrYsmTZqgdevW6NKlC3r16oUXXngBY8eOxXvvvYft27drDM6uzddff40GDRrIzsvKysKyZcvQsWNHODg4wNvbG87Ozqhfvz4mT56sPuI1b9482W1kaYWFhYiNjZWUnz9/vlTLMfazXiwxMVFydCs5OVnv0TFL7Zuvv/5a65isz3rw4AG++uor9XSPHj2wcOFC2brm/A41b95c8vzwmJgYfPrppygsLERGRga+/fZbTJ48Ge+99x6cnJw06sr9PTDku1Ss5N+YoqKiUv2NIzIbQWQkf39/AUD9CggI0JieMWNGmZa/atUqjeUVv77//nud7UaPHi1p4+rqKhISEvSu88mTJ6J9+/aS9p06dRLZ2dl629+9e1e4uLhI2r/yyitCpVLpXG+TJk1k3y8A0axZM1G7dm31tK+vr7h165Y4fPiw1jYAxJAhQ0RRUZHGusLCwnS2Kc3L2tpaDBs2TCQlJencLjExMcLLy8uodfj7+4v8/Hy9294Spk+fLtvngIAAUVhYaPByjP2sF1u+fLls+x07duhta+p988orr2j9TOv6Tt64cUO0bt1aXb9Vq1Y665vzOySEEMOHD5et7+joKOzs7NR/N7KysjTaFRUVib59+0raNWnSRDx69EjP3hJix44dsutdu3at3rZyivta/GrRooXG9LZt24xaLpEQYg1DJhnNFCEzKytL3L17V2zYsEHyx6/45eTkJMLDw0V8fLw6+D169EjExsaK0NBQrT8Wbdu2FQcOHBBJSUkiJydHvc68vDyRmJgojh07JkaMGKG1fUBAgDhw4IBITEwUubm5Gu3PnDkjli1bJjp06KC1/YQJE0R0dLRIS0uTfe+RkZHC3t5e5w+elZWVmDlzpsjLyxNCCJGWliacnJxk6w4dOlTyA1e8jeV+5Mrycnd3F6dOndK5b2/evKkRagKW0gAAIABJREFUGgx5BQUFiczMzFJ/jkylsLBQZGZmit9//11MnjxZZ9/79OkjDh8+LNLT00VeXp56nxUz9rNeLCcnR8THx4uNGzcKW1tbrYFn06ZN4vbt2yIjI0Pr+zLlvpELmW3atBEAhLOzs1i4cKG4evWqyMvLEwUFBSI6OlqsXr1auLm5qev7+fmJ1NRUvesy13dICCEePHggWrZsqXU9zZo1E8nJyUKIp8Hy0qVLYsuWLeKll17S2qZLly5i37594t69e+LJkyfqdT158kQkJCSInTt3irp168q2rVatmti4caOIi4sz6D/ExRgyyYQYMsl4pgiZ9erVK9UPnZ+fn0hJSSl1KOrYsaN6nbNnzy51++DgYHX7Xr16lbp9XFyc7PuPiYkR7dq1k/1h9PHxEUePHpW0+eSTTyTBZMGCBTqPpBUWFooFCxaUut+6Xi1bttT4YZSTl5cnPv30U+Hq6qpzWW5ubuLLL78UBQUF+j4yZjV16tQybaNnGfNZf1b37t1LvX65I3LFTLVv5EJmcnKyeOeddzTCsY2NjXBwcNCo5+7uLr766qtSHRU213dICCFu374tunbtKqysrNRtbW1tRWhoqMZ/Jo8dO1bqfTV06FAhhBD5+fmlbuvr62vw9mLIJBNaYyUER50l4/Ts2RMnTpxQTwcEBODo0aPq6RkzZmDVqlWW6FqFVlBQgJiYGFy4cAGpqano0qUL/Pz8ZG82KHb8+HGcOHECXl5eCAoKgpeXl8515OfnY9iwYfjrr7/U7ZKSkpCcnIzk5GRkZGQgOzsbWVlZyM7OVv87PT1d/YqPj5fcVLF27VqEhITofY+5ubn4/fffceDAASQkJOD+/ftwc3ND/fr1MWDAAAwaNEhyHRuZh9L7ZtSoUdi5c6dGWUZGBlxdXREXF4eoqCjExsYiNjYWycnJ8PT0RMOGDTFo0CD07dvXqOG1zPEdelZqaioOHTqEmjVrwsfHRzK8Unlmb2+vce1vixYtcP36dfX0tm3b8Oqrr1qia1TxfcXB2InKGTs7O/j6+sLX19fgNj179kTPnj0NqltYWIgxY8bg999/x4YNG9CpUycAKNWPKvD0uebnzp3D119/jc2bNwOQjmeojYODA4YPH6717mayHHPuG29vb5MMoWXq71BJnp6eGDNmjFFtiSozhkyiKmbSpEn46aefMGrUKLz11ltGL6datWrw9/eHv78/ioqK8O233+q9i56IiKoODmFEVIWsXLkS4eHhAGDQaW1D9erVCwAUeyQlERFVfAyZRFVEdHQ05syZA+DpE0sCAgIUW/bx48cB/P+wSURExJBJVEVMmzZN4wJ/pe75S01Nxe7du+Hh4YHu3bsrskwiIqr4GDKJqoD4+HicPHlSPf348WMsXbq0zMtNS0tD//79kZ6eju3bt/OOcJJQqVQGlRFR5cOQSVQFyD2ecMGCBRg/frzBd4Q/S6VSYc+ePQgMDMTff/+NxYsXo1+/fkp0lSqZ3Nxcg8qIqPJhyCSqApo0aYK+fftKyrdu3QpfX1907NgRq1atwqlTp3Dnzh1JCCgsLERCQgJOnz6NL774Ai1atMDw4cNx6dIlvPnmm5g7d6653gpVMFlZWZKyzMxMC/SEiMyNQxgRVRE//fQTRowYgcOHD0vmXbx4Ee+++65GWY0aNeDh4YHMzEzcv39fcorT0dERa9aswRtvvGHSflPFpVKpcOXKFUn5xYsX0bx5cwv0iIjMiSGTqIpwdXXF/v37sXXrVqxatQqXLl3SWT8jIwMZGRmScmtra7zyyiv4z3/+g9atW5uqu1pt3rwZYWFhZl9v3759sWzZMrOvt6LKycnBhx9+iPv370vmzZgxAx07dkSzZs0s0DMiMheGTKIqxN7eHpMmTcKkSZPwxx9/YOXKldi/f79BN2K0bt0aQUFBmDZtmkWPQiUnJyM6Otrs6zXFk2kqm/j4eCxevBjR0dGIiYlBYWGhbL179+6hXbt28Pf3R/369TF27FgMHDjQzL0lIlNjyCSqovr374/+/fsjPz8fd+/eRVxcHOLi4nDnzh1kZmbC3d0d7u7u8PLyQo8ePUr92ElT8fHxwZQpU8y+3uLHb5J2dnZ2aNy4MRo3boyXX37Z4Haurq4m7BURWQpDJlEVZ29vj2bNmlWYU5eDBw/G4MGDLd0NkuHl5YV58+ZZuhtEVE7w7nIiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhyf+EMmc+PGDezYscPS3SAiIi2EEJbuAlViDJlkMvv27cO+ffss3Q0iIiKyAJ4uJyIiIiLFMWQSERERkeJ4upwUY2Njg6ZNm1q6G0REZKTCwkJLd4EqEYZMUsyRI0cs3QUiIiIqJ3i6nIiIiIgUx5BJRERERIpjyCQiIiIixVkJjsRKRERERMr6ikcyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYj+H3t3Hhdluf9//MOmgigiaoB6wKVcciURt9BUXI6VZmWStlhptqhZubXoaTc9J/1maUcti/RQ6rGvRmm4hKbijppi7gooAq6JyH79/ujHfB1nBS6YAV7Px2MeD+/luu7P3PeM8+ZeAQDaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2hEyAQAAoB0hEwAAANoRMgEAAKAdIRMAAADaETIBAACgHSETAAAA2rk7ugBUTefOnZMRI0Y4ugwAqNRCQ0Nl9uzZji4DVRQhEw6RlZUlmzdvdnQZAFCpVatWzdEloArjcDkAAAC0I2QCAABAOw6Xw2n07NlTlFKOLgMAKqy8vDyJj493dBmAiBAy4US2b98ueXl5ji4DACqs8PBwR5cAGHC4HAAAANoRMgEAAKAdh8vhtIYNGyb16tVzdBkA4LTi4uIkMTHR0WUAZhEy4bSmTZsmHTp0cHQZAOC0xowZQ8iE0+JwOQAAALQjZAIAAEA7QiYAAAC0I2QCAABAO0ImAAAAtCNkAgAAQDtCJgAAALQjZAIAAEA7QiYAAAC0I2QCAABAO0ImAAAAtCNkAgAAQDtCJgAAALQjZAIAAEA7QiYAAAC0I2QCAABAO0ImAAAAtCNkAgAAQDtCJgAAALQjZAIAAEA7d0cXAMCyc+fOycaNGyUpKUlat24tERERUqtWLUeXhSqAzx6A0iJkAk7q66+/lnHjxklmZqZhXHBwsCxbtky6devmwMoqrw8++EBOnDhh17yjRo2S8PDwMq7IMfjsAdCBkAk4oa1bt8qzzz4rhYWFRuPPnDkjgwcPlsOHD0uDBg0cVF3l5e/vLwcOHJD//d//lby8PKvz9ujRo1KGTD57AHThnEzACU2fPt3kR77IxYsX5dNPPy3niqqGZ599VpYvXy6bNm1ydCna7N27V/bt22f3/Hz2AOhCyAScUEJCgtXpxQkNKL4ePXpI+/btHV1GqaWmpkr//v1l+fLldrfhswdAF0Im4IS8vLysTq9Zs2Y5VVJ1NWnSxNEllIpSSp5++mm5dOlSsdrx2QOgCyETcEL33ntvqaaj9OrWrevoEkrl008/ldjY2GK347MHQBdCJuCEZs+eLbVr1zY7LSQkRF544YVyrqjqqVGjhqNLKLFDhw7J1KlTS9SWzx4AXQiZgBNq3LixbN++XTp16mQY5+LiIpGRkRIbGyseHh4OrA7OLCcnR0aMGCHZ2dklas9nD4Au3MIIcFJ333237N69W1JTUyUpKUlatWplcQ8TUOSNN96QgwcPlqoPPnsAdCBkAk4uICBAAgICHF0GKoDZs2fLJ598oq0/PnsASoPD5QBQCXz44YcyefJkR5cBAAbsyQQ0unLlihw6dEiOHTsmfn5+0qxZM2nbtq3RPElJSXLgwAF54IEHHFSleTdu3JC9e/dKcnKy5OTkSHBwsLRs2VICAwPtap+RkSGHDx+WM2fOSPXq1SU4OFhatWolderUKePKK5bc3Fw5fPiwpKenS0ZGhmRlZUnDhg0lKChIgoKCiv18cKWUvP322/LBBx+UUcX6XLp0Sf744w9JTU2Vy5cvS2BgoDRt2lSaNGkinp6e5VpLWlqaHDx4UJKTk8XHx0datGghLVq04JxTQCNCJqDBvHnzZNasWZKSkmIy7c4775RRo0ZJZGSk1KhRQwYOHCiFhYUmIXP69Oly9uxZu5b3/PPPmzxDetq0aXL+/Hm72i9ZskRcXf86kHHq1Cl55513ZNWqVUbPqhYRcXV1lSFDhsj06dMt3px88+bNMnfuXImJiZH8/HyjaV5eXjJ27FiZNm2a1KtXz2Zdp0+fln/84x92vYeuXbvK2LFjDcPnz5+XadOm2dU2JCREJkyYYNe8OqSnp8tXX30lGzdulO3bt0tWVpbZ+YrW95QpU6Rz5842+7127Zo88cQT8uOPP1qdLyYmRlJTU03G33HHHTJr1qxSf/asKSgokCVLlkh0dLRs3rxZCgoKTOZxcXGRAQMGyIQJE6Rfv37i4uJis9+nnnrKruUHBATIzJkzDcPbtm2TmTNnytq1a01qadKkicycOVOGDRtmV98AbFCAAxw7dkyJiNHLw8PDaDghIcHRZdqUnp6uBg0aZPJegoODVbt27ZS7u7vReBcXFyUiqlGjRiZ9LVy4UD366KMmbcy9vv32W5P2c+fOVV26dLHZVkRUXl6eUkqpBQsWqJo1a9qcv2bNmmrTpk1Gy8vKylIvvfSSXctr1aqVSktLs7k+L1y4oMaOHasCAgJs9vn4448btb106ZJ68cUXVaNGjWy2HTJkiM1aXnzxRat9LF682GYf586dUy+99JLy9PQ0tPP19VUhISGqadOmVvsfPny4ysnJsdj3wYMHVfPmze1a/5ZezZs3V0qV/rNnya5du1SHDh1M+mjZsqUaPny42fpbtmyptmzZYrPv119/XbVq1cpmvS1atFBKKZWdna1ef/115erqarPNxIkT7X6PjjZ69Gij2sPDw42GIyIiHF0iqq7PCJlwiMoQMvPz81W7du2Mag4KClKxsbGqsLBQKaVUZmam+sc//qFq1KhhNF+dOnUs9rtx48ZS/dC/8sorNtvn5uaqiRMnFiuQeHp6quPHjyul/gqDbdq0KVb77t27271ur1y5YjOE3R4yi1y7ds1m+CiPkLlz507l7+9vmN/Hx0ctX75c5efnG+Y5evSo6tevn8Vl9O3bV2VmZprtv2HDhsVa/+ZeRSGzSGk/e7das2aNyXdaRNS//vUvw/dDKaU2b95s8oeOh4eHWrRokc1l5OXlqYEDB1qtt0WLFiozM1P16dOnWOtmwYIFdr1PRyNkwokRMuEYlSFkzpkzx+Q9rFq1yuy8iYmJqm7dukbv1ZqWLVuW+If+999/t/kD+txzzykRUdWrV1eDBw9WX3zxhfroo49UUFCQ1XbDhw9XqamphhDn6+urpk6dqr799ls1duxYVb16davtf/75Z7vX7+uvv16ikKmUUjNmzHBoyNy8ebPR3ksRUf/85z/NzpuZmanatm1rcTmTJ0822+73339XCQkJKiEhQb322mtWax01apRh3ltfiYmJJv2W5rNX5JdffjH7Wejfv7/Z+RcsWGB2WW+88YbNZX333XdW623cuLHq3r27EhFVu3Zt9cILL6h33nnH7B7WW1/+/v5W9yQ7C0ImnBghE45R0UNmRkaGql27tsl7iI+Pt9hmy5YtRj+82dnZFue1tXfG2g/9n3/+aTNkioiqX7++Sb2HDx+2GhRdXFxUcHCwEhHVp08fdenSJaP2//jHP6wuMzIy0s41rNS8efNKHDK//PJLh4XMrKws1axZM5P5H3vsMYvLevPNNy0up1q1aiopKclqrZ988onVWqdMmWLz/RYpzWdPKaXS0tKUn5+f2baxsbFm22RlZSlfX1+zn7fbT9O43fbt2+36vHfr1s3olI3c3FyTIxG3v77//nu715ujEDLhxD7jFkZACWzfvl3+/PNPk/HffPONxTb33nuvzJ492zB8+0U2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c+dOl+uIj4/X/PnzNW/ePJ08edJp3+joaD322GM6depUjn2ysrK0YMECZWZm6ssvv8xuT0tL06hRozR9+vRcv0e/fv20bNky9e7d2+XvUVTyO8/OfPPNNxoyZEj2v+Nu3bpp2LBhatmypQIDA3X27Fl99tlnmjhxonbt2qXmzZvL39/flH0DKFkImYCbMwxDTz/9tBISEvS3v/1NW7ZsUVBQkE2fatWqacyYMXryySfVpk0bxcbG5ri9EydOqFGjRrp69Wqe6li5cqUeeeQReXh4KD093Wnfn376SQ899JCuXbum5s2bq1mzZvLz89PWrVu1detWpaam2vRfuHChOnbsqCeffFIZGRnq37+/li9fLkkqW7asWrVqpZCQEG3btk2HDx+2GZuZmamhQ4eqa9eu8vX1zdN3Kkz5nWdnFi1apH/84x8yDEMeHh4aP3683n77bZu7xytVqqQJEybo3nvv1cMPP6z09HRdvnzZtBoAlBzc+AO4uVmzZmnTpk2SpNGjR9sFzJtVqVJFixYtyj7C6UhYWJhWrFihpKQkJSQkqFu3bi7V8eCDD2rz5s26cuWKYmNjdeeddzrst379enXv3l1NmzbVrl27FB0drRkzZmjKlCnatGmTfvnlF4WGhtqNmzt3rrKysvT0009r+fLl6tmzp7Zu3aqLFy/qv//9rxYtWqQ//vhDq1evtnuDTVxcnD755BOXvkdRye885+Q///mPBg0aJMMwJEkjR47Uu+++m+Pjibp3766FCxfy+CIAObP4fH2h4ppMIHft2rXL/j2sW7fOpTGjRo0yJLl0TeYvv/xi97tz5VrBKVOm2I1r2rSpERwcbMybN8/IysrKcezu3bsdXmP58MMPG35+fsbixYud7nvZsmV2Y9u3b59rzVbK7zwbhmEkJiYad9xxR/a4cuXKGRcuXHBp7OOPP263X67JBLgm0zCMmfwvKODmfv/99+x/jomJcWnM4MGDXd5+3bp181yTJNWrV8+u7cCBA1q2bJmee+45eXh45Di2cePGatmypV37li1btHHjRj3xxBNO992rVy+7u8q3b9+urKwsF6svevmdZ0kaPny4zp8/n708ZswYl++q7969e773C6BkI2QCbiw+Pl4XL17MXp4+fXqu10NKUu3atdWsWTOX9hEcHCwfH58811axYkW7tkGDBql9+/Yuje/cubNd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+HG7try8ChMACgMhE4ClDMNQfHy8VqxYkWtfT09PjRs3TmPHjrVbl9NrG/PjgQcecHiqedy4cXZHDHOTkpKiyZMnq2fPnvL0dO1PblpaWp72ceLECZtlX19fdevWLU/bAACzETIBFAt5eT3km2++qcqVK9u0BQUF2fVzFOpceexRhQoV1L17d7v2ffv2qV+/fi4faczKytLAgQMVFxen3r17uzRGksPXTzpz62ss//GPfyg0NDRP2wAAsxEyARQLa9eudfmaRR8fHzVt2tSmLSIiwq6fo7urExMTHW4zJibGJty99tprDvt999136tatm06ePOm0xrS0NA0ZMkTfffed6tatq06dOjntf7Pctn2z8+fP67PPPste9vLy0quvvuryeAAoLIRMAMVCWlqaBg4cqKysLJf633rkslmzZrn2kf4Ms7datGiR2rVrZ3OKvG3btjm+SnL9+vWqX7++Ro8erT179tjcOX769GnNmzdP4eHhmjdvnry9vbVw4cIc7453ZOXKlS73/fDDD23C8fjx4/W3v/3N5fEAUFgImQCKjQ0bNujtt9/OtZ9hGIqOjs5e7t+/v6pUqWLXLzw8XL6+vjZtv//+u9577z1lZGTo0qVL+uKLLzR48GC9+uqr8vf3t+n7+eef684773RYw9WrVzV16lQ1btxYvr6+CgsLU0BAgKpVq6bBgwdnH40cN26cwwDszOeff+7wZp5bnT9/XrNmzcpebtGihd5888087QsACgshE0Cx8s4772jkyJFOr0ucMmWKDh8+LElq3Lixzenim5UuXVpdunSxax8/frwCAwNVoUIFDRo0SHXq1NFLL71k1698+fKKjIxUpUqVnNaclpamkydP2tXcsmVLvfHGG07HOnLlyhV16tTJ6duMjhw5ojZt2mQ/H7Nu3bpaunSp3Z33AGAVQiaAYiUrK0vTpk1TgwYNtGDBAsXExCgzM1OGYSg2NlbPPfecxo0bJ0mqXbu2fvjhB7sjkDebP3++6tSpY9d+48YNpaenq3bt2lq9erXKlCnjcHyjRo20ZcsW1atXL0/fo1OnTlqzZo18fHzyNE6S7r77bh05ckR169bVP//5Tx06dEhpaWnKyMjQrl27NGPGDEVERGTfUd+8eXNt3rxZVatWzfO+AKCwEDIBFAtVq1ZV69atVbZsWUl/PvvxmWeeUf369VW2bFkFBASodu3amj9/vgIDAzV16lTt378/x/d2/yUkJESrV69Ws2bNbK659Pb21tChQ7V169Zcn19Zs2ZN7d69W++9916ur2oMDg7WjBkz9J///EcBAQGufflbbNiwQS+++KJSUlL01ltvqW7duvL391fZsmXVpEkTDRs2TElJSQoJCdGsWbO0ZcsWVahQIV/7AoDC4m11AQDcW6lSpTR37lz16dNHISEhysrK0oEDB/Trr79q165dOnv2rM6dO6dy5cqpQYMGatCggTp37pynUFW9enVt27ZNiYmJ2rBhg4KCgtSkSZM8PeanVKlSGjt2rEaMGKE1a9Zo7dq1iouL07lz5xQcHKxq1aqpc+fO6tq1q9Mjq67w8/PTrFmzNHr0aEVHRysmJkYxMTE6e/asQkNDddddd6lr165q3759vo6UAkBRIGQCsJSXl5eef/757GVPT0/Vr19f9evXN31foaGh6tevX4G24evrq169euV457mZwsLCHL7iEgBuB5wuBwAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMALCQo9douvpqTQAozgiZAGChlJQUl9oA4HZDyAQAC129etWu7a9XRQLA7YyQCQAW+evB87fas2ePBdUAgLkImQBggevXr+u1117TuXPn7NYNHz5cR44csaAqADAPb/wBgCJy6tQpvfPOO9q5c6d+//13ZWRkOOx3+vRpNWjQQC1btlS1atU0YMAAdenSpYirBYCCIWQCQBHx8fFRjRo1VKNGDT3yyCMujwsMDCzEqgCgcBAyAaCIVKpUSePGjbO6DAAoElyTCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA07n9G38mTJigUqVKWV0GAAAoQbZt22azfPXqVYsqsY5bhcysrCy7tlmzZllQCQAAcCdnzpyxuoQix+lyAAAAmI6QCQAAANO51elyR0qXLm11CQAAoITJzMxURkZG9rI75g23CpmenvYHbs+ePaugoCALqgEAACXVpEmT9Prrr2cvh4WFKSEhwcKKih6nywEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmM7b6gIAID8iIyN18OBBNWnSRO3bt7e6HADALQiZAG47ffv21bJly7KXR4wYoWnTpllYEQDgVpwuB3BbWb9+vU3AlKSPP/5Yhw4dsqgiAIAjhEwAt5WjR4/mqR0AYA1CJoDbSosWLeTpafuny8/PT/fee69FFQEAHCFkAritNGjQQNOmTZOPj48kyd/fX/PmzVOlSpUsrgwAcDNCJgDLzJ49W+vXr8/zuGHDhun06dPasmWLTp06pQEDBhRCdQCAgiBkArDEtWvX9Prrrys2NjZf4ytUqKCWLVuqfPnyJlcGADADIROAJRYuXKjk5GSrywAAFBJCJoAil5aWpsmTJ1tdBgCgEBEyARS5yZMn68SJE1aXAQAoRIRMAEVq5cqVevPNN60uAwBQyAiZAIrMvn37NGDAABmGYXUpAIBCRsgEUCTOnTunnj176urVq1aXAgAoAt5WFwCg5Nu9e7f69OmjY8eO2a27fPmyEhIS7NoDAgJUpkyZHLd56dIlLVy4UAsXLlRoaKh+/PFHl+s5ceKE5s2bp+XLl6t///5644037PocPnxYq1ev1m+//aY9e/YoNDRU9erV06BBg9S4cWOn28/IyFBkZKRWrFiho0ePKikpSWFhYWrRooWGDBliymOX0tPTtXHjRq1cuVKHDh3S2bNnlZWVpYYNG6pRo0Zq1KiRWrdurXLlyhV4XwCQL0YJFhUVZUjK/lSsWNFmWZKRlJRkdZlAiZWUlGSMGTPG8PX1tfvt5fZ56623HG5z27ZtxjPPPGP4+/tn923YsGGutWRmZhqrVq0yunfvbnh6emaPHTp0qE3fuBaMAAAgAElEQVS/q1evGqNHjzZ8fHwc1uXp6WkMGjTISE1NdbifFStWGJUrV87xe5UpU8ZYunRp3ifzpu8xd+5cIzQ0NNc5rFy5shEZGZnvfQHIv4kTJ9r8HiMiImyWw8PDrS6xsM3kSCaAQvPdd9/pyy+/VFBQkCQpNTVVSUlJNn0CAwPl7+9vNzYgICD7n69du6avv/5ac+bM0c6dO/NUQ3x8vObPn6958+bp5MmTTvtGR0frscce06lTp3Lsk5WVpQULFigzM1NffvlldntaWppGjRql6dOnO93HtWvX1K9fPy1btky9e/fO03dJSEhQz549tW3bNnl5een555/XAw88oEaNGuncuXOKiorSnDlzdOXKFUl/fvcePXpo0KBB+vjjjxUYGJin/QFAgVgdcwsTRzKB4mXVqlV2v8FPP/3U6Zjjx48bgYGBhqenp80RyJs/OR3JXLFiheHt7Z3jUUnddCRzw4YNRpkyZQxJRvPmzY2XX37ZGDVqlNGmTRujdOnSDscuXLjQMAzDSE9PN/r06ZPdXrZsWaNr167GgAEDjPDwcIdjq1WrZty4ccPluTty5IhRs2bN7CMg0dHRDvvFxsYaFSpUsNtfp06dXN4XgILjSKYxkxt/ABRrYWFhWrFihZKSkpSQkKBu3bq5PPbBBx/U5s2bdeXKFcXGxurOO+902G/9+vXq3r27mjZtql27dik6OlozZszQlClTtGnTJv3yyy8KDQ21Gzd37lxlZWXp6aef1vLly9WzZ09t3bpVFy9e1H//+18tWrRIf/zxh1avXi0/Pz+bsXFxcfrkk09c+h43btxQz549dfToUQUHB2vTpk1q3ry5w741a9bU999/r9KlS9u0r127VosXL3ZpfwBgBkImgGLvwQcfVGBgoO644w6HN+nkJDAwUC1atFDp0qVVs2ZNDR061K7Pr7/+qr59+2r69On66aefdM8999j1adKkiaKiouzaf/75Zz366KNavny5Fi9erBUrVigiIkLe3rZXInXp0kVfffWV3fjIyEiXvseIESMUExMjSZo+fboqV67stH+rVq00bdo0u/aRI0faXa4AAIWFkAngtlK3bt18j61Xr55d24EDB7Rs2TI999xz8vDwyHFs48aN1bJlS7v2LVu2aOPGjXriiSec7rtXr152d5Vv375dWVlZTsfFxMRo7ty52fUPHDjQaf+/9OvXT56etn/iExMT9cEHH7g0HgAKipAJ4LYSHBwsHx+ffI2tWLGiXdugQYPUvn17l8Z37tzZru39999XRERErmO9vb3tTnFfuXJFly9fdjru5iOSbdu2dalO6c95cnR5QF5vnAKA/CJkArjt3Hq9oat8fX3t2pwdvbyVo+syMzIyXB4fEhJi1/bXneCO3LhxQ4sWLcpebtOmjcv7kqQqVarYtR04cCBP2wCA/OIRRgBuO/k9kpnfcX9xFDIzMzNdHu/oIezOQub27duVkpKSvfziiy9q+PDhLu/v0qVLdm0nT57UtWvXnD7oHgDMQMgEcNu59caawh73l1vvEM8rR0dg09PTc+y/ZcsWm+XatWub8gafxMRE1ahRo8DbAQBnCJkAbjteXl5FOs4q+/fvt1meMGGCevXqZVE1AJA3XJMJAMXUxYsXbZaPHj1qUSUAkHeETAAopm59piUhE8DthJAJAMXUjRs3bJYJmQBuJ4RMACimgoODbZZjY2MtqgQA8o6QCQDF1K2PPDp06JC2bt1aoG0ahlGg8QDgKkImABRTYWFhdm2jR4/O9/ZSUlJ07733Kj4+viBlAYBLCJkAUEy1bt3arm3z5s2KjIzM1/Y+++wzxcfHO3y9JgCYjZAJAMXUAw884PC1l+PGjcvT6yylP49iTp48WT179pSnJ3/6ARQ+/tIAKDKOwo2jVx/iTxUqVFD37t3t2vft26d+/fo5fVvQzbKysjRw4EDFxcWpd+/eZpcJAA4RMgEUmcDAQLu2xMREh31jYmJ07do1h+tuDVeuhi1HR//yckQwKyvLri0v7y53ND632l977TWH7d999526deumkydPOh2flpamIUOG6LvvvlPdunXVqVMnl+sFgIIgZAIoMkFBQXZta9eutWtbtGiR2rVr5/BUsSRdvXrVZjmnMJrbuLyMlRwHwlufZemMo0Cb2/7btm2b46sk169fr/r162v06NHas2ePzZ3jp0+f1rx58xQeHq558+bJ29tbCxcuVKlSpVyuFwAKgpAJoMiEh4fL19fXpu3333/Xe++9p4yMDF26dElffPGFBg8erFdffVX+/v5220hMTFRqaqpdmytHM0+dOmXXdvbsWZfrv/U1j5J0/vx5l8cnJyfbtZ0+fTrXcZ9//rnuvPNOh+uuXr2qqVOnqnHjxvL19VVYWJgCAgJUrVo1DR48OPtI57hx49SsWTOXawWAgiJkAigypUuXVpcuXezax48fr8DAQFWoUEGDBg1SnTp19NJLLzncxpIlS+zaUlNTtXz58lz3v3jxYru26Ohol4JeSkqK/vWvf9m1r1mzRtevX891/KlTpxzW+P333+f67Mry5csrMjJSlSpVctovLS1NJ0+etDs62rJlS73xxhu51ggAZiJkAihS8+fPV506dezab9y4ofT0dNWuXVurV69WmTJlstddu3ZNp06d0rx58zRq1CiH233mmWf01VdfKS4uzib0Xbp0SQcOHNCwYcP07bff2o27fPmyunTponXr1uns2bM2p7/T0tIUHx+vLVu2qH///tq7d6/d+N27d6tr165at26d4uPjbY6ypqWl6bffftMHH3ygHj166MqVK3bjly1bpkGDBmnXrl0Oj5T+pVGjRtqyZYvq1auXYx9HOnXqpDVr1sjHxydP4wCgwIwSLCoqypCU/alYsaLNsiQjKSnJ6jIBt3Ps2DGjWbNmhoeHR/Zv0dvb2xg6dKhx4cIFu/5Vq1a1++06+zRv3twwDMNISEjI0zhJRuPGjbP3O3r06DyP79+/f/b4Nm3a5Hn8iRMnnM5damqq8d577xmBgYFOtxMcHGzMmDHDSE9PN+nfGoC8mDhxos1vMiIiwmY5PDzc6hIL20zvAiVUAMiH6tWra9u2bUpMTNSGDRsUFBSkJk2aKDQ01GH/uLi4fO0nNDS0QK9RnDx5siZPnpzv8Zs2bcr32JyUKlVKY8eO1YgRI7RmzRqtXbtWcXFxOnfunIKDg1WtWjV17txZXbt2dXhNKwAUFUImAMuEhoaqX79+VpdxW/L19VWvXr1yvPMcAKzGNZkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmI6QCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpvqwsoSoZh2LW1atVKnp5kbQAAYJ5z587ZLCcmJlpUiXXcPmTGxMRYUAkAAHAnycnJVpdQ5DiEBwAAANMRMgEAAGA6tzpd7uHhYdd29913c00mAAAw1blz55SQkJC9HBQU5HanzN0+ZG7ZskVBQUEWVAMAAEqqSZMm6fXXX89eDg0N1fHjx60ryAIcwgMAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmI6QCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpvqwsAAJQMZ86c0ZIlS1S2bFkNHjzY6nIAWIwjmQCAfDMMQ+vWrdMjjzyisLAwvfbaa5o3b57VZQEoBjiSCQDIs4sXL+qLL77QnDlzdPjwYavLAVAMETIBAC67ePGiRo4cqW+++UYpKSlWlwOgGON0OQDAZeXKlVNwcLDmzJmjr776SrVq1bK6JADFFEcyAQAu8/Ly0rRp07KXPT09NWDAAAsrAlBccSQTAEw0e/ZsrV+/3uoyikzXrl2tLgFAMUXIBACTXLt2Ta+//rpiY2OtLqXIBAcHy8PDw+oyABRDhEwAMMnChQuVnJxsdRlFysPDQ6VLl7a6DADFECETAEyQlpamyZMnW12GJXx8fKwuAUAxRMgEABNMnjxZJ06csLoMS3h68p8SAPb4ywAABbRy5Uq9+eabVpdhGa7JBOAIIRMACmDfvn0aMGCADMOwuhTLEDIBOELIBIB8OnfunHr27KmrV69aXQoAFDs8jB2AnYMHD+qHH37Qjh07FB8fr0uXLiksLEzh4eEKDw9Xnz59VKlSpQLt48KFC/r3v/+tAwcO6OjRozp58qSCgoJUuXJlhYeH6+GHH1azZs3yvf39+/drzpw5WrNmjSZMmKB//OMfdn02b96sb775RjExMUpOTlaNGjXUrl07Pfvss/L393e6/d27d6tPnz46duyY3brLly8rISHBrj0gIEBlypRxuL0//vhDn376qX788Ue99tpreu6557LXpaamaubMmVq/fr0SExN177336o033tBdd92V2zRIKvy5BgCHjBIsKirKkJT9qVixos2yJCMpKcnqMoFiY8uWLUbz5s3tfie3fsqUKWO88cYbxuXLl/O8jz179hiDBg0yfH19c91P9erVjS+++MLlbaekpBiLFi0yWrdubbOd6dOn2/TbuXOnXZ+bP6GhocaOHTsc7iMpKckYM2aMS/Xf+nnrrbdstpWWlmZ88803Rrt27Wz6TZ06NbvP3r17jXr16tltKygoyIiNjbVsrm8WHBxss62mTZvmaztASTJx4kSb30VERITNcnh4uNUlFraZnC4HoPT0dA0ePFitWrXS1q1bFR4erilTpuinn37S2bNntXv3br311lsKCAiQ9OdDx9955x3VqVNHR44ccXk/c+fO1X333acFCxbIz89P48eP1969e3Xt2jX9/vvvmjFjhjp37pzd//jx43r66afVoUMHxcXF5bjdw4cPa9SoUapataoGDhyon3/+Oce+CxYsUMuW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pKamqjFjxjhU6+npqTZt2pSr+YyLi7P/0vf09FTPP/+8WrVqlTp69KjasWOHGj9+vCpTpoxun0OHDtWEJiNRUVH23zf16tVT33zzjcrIyLB//+LFi2r27Nm6wJk5ZJr1muf3sUdGRqqJEydmGVjv1MTExKj69es79NrWrFlTHTt2zJmXFChUCJmETEImiqyvvvpK9/P80UcfZVtz/vx55e/vrzw8PDRnIXMKmZs3b1ZeXl5ZnpW8O2R+9913qlSpUkpEVPPmzdWoUaPU+PHjVZs2bVSJEiUMa1etWqWUUio9PV317t3bPl6mTBnVqVMnNWDAABUSEmJYW6VKFXX79m2n5u6PP/5QNWrUsP+i/+mnnwzXi4yMVEFBQbp9dujQIctt22w29cgjjygRUQ888EC2v2NiYmJUzZo1swyZmeXmNc/MzGO32Wyqa9eu2Z6VvBMyr127Zt+vo19+fn4ETRRZhExCJiETRVZuA8fu3btVYmKiunLliurcubNDITMxMVH9+OOPKiUlRUVGRhqepRw9erTauXOn8vX1VY888oj69ddfddv55ZdfDM92Pfzww8pqtaoBAwYoEVHdu3dXBw8eVOnp6Zr6bdu2KV9fX139u+++6/C83bp1S9WrV0+JiAoMDFSxsbHZrr9v3z7DcLx69WrD9d9//337OkuXLs2xnwMHDihPT88CCZn5cew7duxQkZGRKjIyUg0fPjzLkNm3b18l8vfbA3r06KHmzp2rVq9erYYPH67uv//+LINmw4YNVUpKisPHCBQWhExCJiETRZYZZ7V+/PFHh0JmZm+99ZaurmnTpiowMFAtWbJE2Wy2LGuPHj1qGCZ69eqlfH191Zo1a7Ld9xdffKGrbdeuncPHPGzYMHvdJ5984lDNwoULdfsMDg5W169f16179/sRd+7c6dD2x48fXyAhM7+PPS0tzfB9tHe20bdvX8PL7bdu3bKHUKOvN954w+FjBAoLQqZawCOMADdWp06dXNXVrVtXN/bbb7/JF198If/85z+zvTP4wQcflJYtW+rG9+/fL3v27JGnn34623336NFDd1f54cOHxWaz5dh3RESELF682H4MAwcOzLFGROSpp54SDw/tr8vLly/LnDlzdOseO3ZMsz9HDBs2zKH18qIgjt3b29vwZ2rcuHEyZswYWb9+vfj7++u+7+vrK5999pkMHz7c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JoqwHj16GIYEX19f5e3trURENW7cWCUlJRnWz50717B+3bp12e73ySef1NX4+/urP//8M8eeb9++rRo2bKirb9y4sUpOTs6x/sKFC5ogcufriSeeUDabLcd6pZQ6duyYqlixYo5hy+irZcuWWYbwzCHTw8NDvfjii1nOv1JKzZ49277+gw8+mOMc5PU1z69jv8MoZIqIqlWrVrY/H2fPnlV169a1r1+nTh2Hfp6AwoqQScgkZKJIu3r1qqpdu3aWoaBWrVoqLi5OU5OUlKQuXLigFi9ebA8lmb/8/PzUqlWr1MWLF+2h58aNGyoiIkKNHj06y/3Vr19f7dixQ126dEndunXLvs/U1FQVGxur9u3bp3r27JllfevWrdWOHTtUbGysSklJ0dQfPnxYvf3226pRo0ZZ1g8ePFgdOXJEXbt2Lce5i4yM1IQaR746dOigbt68meU2M4fMO1/Vq1dXy5YtU6dOnVIZGRnKZrOpP/74Qz333HPKYrHYXytHzvbl5jUviGO/wyhk1qtXT4mIKl26tHrllVfU6dOnVWpqqkpPT1dHjhxR8+fPV4GBgfb1mzdvri5fvpzjvoDCjJBJyCRkosiLiopSzZo1s4cVEVFeXl5q9OjRhmHr3nvvdSpcNG/e3PCyek5fDz74oH2fEyZMcLr+7svGbdq0cbo+Ojo6x7lLTU1Vb7zxhvL39892W4GBger9999X6enp2W7vTsi899571cMPP2x4xtXX11f5+fnZl8uWLavefvttlZqamm+veUEc+x1GITMuLk6NGDFCeXl52cc8PT11l/7LlSunPvjgAy6Ro1ggZKoFFqXy8bPBXGzHjh3SsWNH+3KFChUkPj5es05CQkKWjwsBipLLly/Ld999JwEBARIaGsojX5yQkpIi3377rezYsUP+/PNPuXLligQGBkqVKlWkY8eO0qlTJ/Hz88txO1arVZYtWya9e/eWcuXKic1mk99++00OHDggv/76q8TFxcmVK1ekbNmy0qBBA2nQoIF07NhRgoKCctW3Ga+5Wcd+R9++feWLL77QjCUmJoq/v79ER0fLTz/9JBERERIRESFxcXESHBwsVatWlU6dOkm7du3E29vb6WMACqNZs2bJ1KlT7cthYWFy6NAh+3JISIj8/vvvrmitoHzg5eoOAJgjODhYnnrqKVe3USSVLFlSevTokeXd147y9PSU559/3r7s4eEh9evXl/r16+e1RUNmvOZmHbsjqlWrZvgRlwCKJx5hBAAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAADTGH2UpqMfrwmgeCFkAgBMk5KS4tAYgOKPkAkAME1SUpJu7ObNmy7oBICrETIBAKa48/D5zI4fP+6CbgC4GiETAJBnt27dknHjxsmVK1d03xs7dqz88ccfLugKgCvxiT8AgFy5ePGivP7663LkyBE5duyYZGRkGK4XExMjDRo0kJYtW0qVKlVkwIAB8vjjjxdwtwAKGiETAJAr3t7ecv/998v9998vffr0cbjO398/H7sCUFgQMgEAuVKxYkWZPHmyq9sAUEjxnkwAAACYjpAJAAAA0xEyAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJjO7T/x5+zZs1KmTBlXtwEAAIqRq1evapatVquLOnEdtwqZNptNNxYWFuaCTgAAgDuJjIx0dQsFjsvlAAAAMB0hEwAAAKYjZAIAAMB0bvWeTA8PfaY+dOgQN/4AAABTLV68WN577z37cs2aNeWXX35xYUcFz61CppGQkBAJCAhwdRsAAKAYKV++vGbZ09PTRZ24DpfLAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACmI2QCAADAdIRMAAAAmI6QCQAAANMRMgEAAGA6QiYAAABMR8gEAACA6QiZAAAAMB0hEwAAAKYjZAIAAMB0hEwAAACYjpAJAAAA0xEyAQAAYDpCJgAAAExHyAQAAIDpCJkAAAAwHSETAAAApiNkAgAAwHSETAAAAJiOkAkAAADTETIBAABgOkImAAAATEfIBAAAgOkImQAAADAdIRMAAACm83J1AwBQ0NLT0+X69etO1QQHB4vFYhEREavVKlevXs12/RIlSkhAQECuewSAoo6QCcDtHD58WFq1auVUTVJSkpQqVUpERKKjo6VmzZrZrt+jRw/58ssvc90jABR1XC4H4HZatmwpERERMm/ePGnXrl22677++uty6dIle8AUEalRo4acO3dOnn32Wd36vXr1knXr1sknn3xiet8AUJQQMgG4pbp168qYMWNk165dMnbsWMN1PDw85KWXXpKKFSvqvnf//ffLyJEjNWNz5syRjRs3Sr9+/aRMmTL50jcAFBWETABub+7cudK/f3/duM1mk61bt2ZZt3r1avt/v/vuu/LSSy/lS38AUBQRMgFARD766CMJCQnRjb/00kuSlJSkGz969KgsWLBARESGDBkiL774Yr73CABFCSETAESkTJkysn79evHx8dGMX7hwQSZNmqQZS09PlyFDhkh6erqEhYXJokWLCrJVACgSCJkA8F+NGzeW119/XTe+cOFC+fHHH+3L06ZNk2PHjkm5cuXkiy++kBIlShRkmwBQJBAyAeAu48aNk9atW2vGlFLyr3/9S9LT02XXrl3y9ttvi8VikZUrV8p9993nok4BoHAjZALAXTw8PGTlypVSunRpzfjJkydlypQp8swzz4hSSl566SXp0qWLi7oEgMKPkAkAmdx///0yd+5c3ficOXPk0qVL0qJFC3njjTdc0BkAFB2ETAAw8Nxzz0m3bt10415eXvLpp5+KlxcfmAYA2SFkAkAWlixZIuXLl9eMZWRkyMaNG13UEQAUHYRMAMhChQoVZN68ebrxKVOmyMmTJ13QEQAUHYRMAMjG5s2bdWOpqanyzDPPSFpamgs6AoCigZAJAFlYuHChrF+/XoKCgqRatWqa7x09elRefvllF3UGAIUfIRMADBw5ckT+85//iMVikdWrV8uKFSvEYrFo1nn77bdl3759LuoQAAo3QiYAZJKYmChPPvmkpKamypQpU6Rjx47Stm1bGTNmjGY9m80mgwYNkps3b7qoUwAovAiZAJDJc889J5GRkdKmTRt59dVX7eNvvPGG1KlTR7NuVFSUjB07tqBbBIBCj5AJAHd5//33ZcOGDRIUFCRr164VT09P+/d8fX1l5cqVmjERkeXLl8uXX35Z0K0CQKFGyASA//r5559l3Lhx9vdhVq5cWbdOWFiYTJ48WTc+bNgwiY+PL4g2AaBIIGQCgIhcu3ZNnnjiCUlLS5Nx48ZJx44ds1z35ZdflgYNGmjGrly5IkOGDBGlVH63CgBFAiETgNuzWq3Sr18/iY6OlsaNG8uMGTOyXd/b21sWL14sHh7aX6Hbtm2Tt956Kz9bBYAig5AJwO1NmDBBdu3aJSIiH374ofj4+ORY06JFC3nyySd149OmTZP9+/eb3iMAFDWETABubebMmfLuu++KiIin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9NNPW53/8uXL6qabbjIrU7t2bTVjxgw1a9YsNWvWLDV79my1fPlyTdnK2Afu5kzIVEqpXbt2qdzcXJWdna2io6Ptei/k5uaqn3/+WeXn56vExEQCpzO0AAAgAElEQVTdq5RTpkxRO3bsUH5+furOO+9Uv/76q2Y5Bw8e1L1C1bdvX1VSUmL6p2PYsGFq3759mivS3377rfLz89OUf+ONN+zebp7QF7Zv364SExNVYmKi7nta5HrIHDVqlBK5/vHx8OHD1dKlS9XatWvVpEmTrH7tp2vXrio/P99qG6rTcc3VSkpKzALm3XffbfMiRteuXTXrcN9991mc35X9mJBZ5RAynQmZjz32mNkybr31VptlBg8erKnb1n9/KSkpmv+Uvby8VHZ2tkPt/b//+z8lIuquu+6yOt/EiRNN9Xz88cd2LXvFihWa9QoJCVE5OTm68+t97/Tee+9Vvr6+6quvvjKbd9euXap///6qc+fOavny5WYnclftg6SkJM0J5Pjx4zbr+te//qWp67PPPrNZzhX7wNWcDZll/fzzz3aFzPJeffVVTblevXqp4OBgtWrVKqsn1EOHDukGm3vvvVf5+fmpTz75xGrd69ev15SNioqye509rS8UFhbqfnf1RptHjRql+7Ho1atXTSFU7/XKK69Yrbc6Hddcbfbs2Wbnll9++cVmmSeffFKzrgEBARbDviv7MSGzyiFkOhMy27dvb7aMtm3b2ixT9o1242XPG3rMmDGacjc+snO0vevXr7c4z9GjR03L79ixo93LzsnJ0f04c/bs2brzr1u3TvdEYevqoKV1qux9MHPmTLP5vby8rH7d4IacnBxNXR06dFAlJSUWy7hqH7iaESFTb3vaEzL16g4ICFBxcXF21dunTx/dk92+fftsli0qKtJc0Q4MDLTaB27w1L5Q/scxItc/9rb1/i8tLbV4JbR58+aqsLBQt1x1O665UkJCgtlXGUaPHm1XuZUrV+qu64kTJzTzurofEzKrnLe4T6YTevbsaTbcr18/m2X0bvdw9epVm+UmT56sGbdixQqb5W7YtWuXnDhxQpo1aybDhw+3ON+SJUtMf9955512Lz84OFhatmypGR8fH687f7169TTjbr75Zpk/f77ddYq4bh8cPHhQs4zatWvbrCs4OFgaN25sNu748eNy5swZi2VctQ+qo+DgYKeeK1x+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hdp9Ziw1ix5LS0tKT0/XOvbLL78UfX/Ozs6UlJSkMm7ixImCGEdHR7pz547GMcvKysjHx0cQ37VrV43HARFRVlYWOTg4COInTJhAcrlcq/e9YcMG0fe9d+9ejbHdunUTxHXo0IEePXqkMfbHH38UHXfTpk1a5f284SKTqcBF5ouKi0yh2kVm9at169a0fft2un79OlVVVZFcLqebN2/S9OnTSSKRKIq96plFde7du0evvPKK2qIxPz9fKeb48eNkY2OjtliSSCT0wQcfKAqmoqIiwT6ufo0aNUplkViX/GpLT08nLy8vnYq9wMBAevz4sWh/MTExojOs+njNnj1b4z4ztD/++IMmT54syM3f359++OEHlduFiOj27dsUHh5OL7/8ssr36OTkRDNnzqTo6Gh68uQJPXz4kFJSUigkJERlTOfOnSkuLo7y8vKotLRUMV5FRQXl5ubSqVOnaOzYsSrj+/XrR3FxcZSbm0vl5eVK8efPn6d169ZRly5dVMa//fbblJSUREVFRYL3XFJSQllZWRQREaH4w6f2y87OjqKioig7O1tR8MrlciouLqYLFy6IzvRXvwYNGkSnTp2igoICpdzLysooOzubfvzxR5V/SFlbW9PmzZspIyND7X573nCRyVTgIvNFxUWmUHWR2bx5c+rbt6/oLIutra3StnNycqJ169bpNBuWkZFBPXv2VBSowNOvNkNCQkQ/VImezhB6e3uLFpfdu3enP/74QxDz2WefCT54ly9fTlVVVXrPr7aKigr6/PPPydHRUW2R5+zsTGFhYSSVSlX2NXDgQIMUmIBuX0kbQllZmcYcV65cqTK+Q4cOOr3f0NBQnbeRr6+vYryFCxfqHF/zFJL+/fvrHJ+Zman0nps3b65TfK9evYiI6NSpUzqP/dprrynGtbCw0CnWzc1NX78mZo+LTKbCZgmRge7QzMyavb09SktLFcudOnVCSkqKYnnHjh0IDg42QWamI5PJsH37dowfPx4uLi6Qy+VITU1FYmIiLl26hPz8fNy9exdOTk7w9vaGt7c3hg4dCldX1zqNV1hYiJMnT6Jx48bo3r073Nzc1LaXSqW4fPkykpOTUVhYiP/7v/9Dr169VN4yCXj6SMozZ87A3d0dgYGBcHd3N1h+YsrLy3Hs2DHExcXhzp07uHv3LpydndGiRQsMHToUw4cPh52dnc79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KRKqaZhkEgCgpKREZbu0tBSlpaVmioaIiJ5V/Oygcvy6nIiIiIgMjkkmERERERkcvy4nAICLiwvS0tKU24MHD0bnzp3NGBERET0L9u7di6ysLOW2g4ODGaOhmoRJJgEA7OzsVLYDAgIwefJk8wRDRETPjJMnT6okmfXq1TNjNFST8OtyIiIiIjI4JplEREREZHBMMomIiIjI4JhkEhEREZHBMckkIiIiIoNjkklEREREBsckk4iIiIgMjkkmERERERkck0wiIiIiMjgmmURERERkcEwyiYiIiMjgmGQSERERkcExySQiIiIig2OSSUREREQGxySTiIiIiAyOSSYRERERGRyTTCIiIiIyOCaZRERERGRwTDKJiIiIyOCYZBIRERGRwTHJJCIiIiKDY5JJRERERAbHJJOIiIiIDM7a3AEQERFVR1RUFK5duwZPT0/4+PiYOxwi+guTTCJ6LmRnZ+Py5ctIT09Hx44d4ebmBnt7e3OHRUb2+uuvY9++fcrtDz74AF988YUZIyKicvy6nIieWfHx8Rg6dCicnZ3RpEkT+Pr6YsqUKejduzccHBzQunVrjB49GleuXDF3qGQEx48fV0kwAWDdunVITk42U0REVBGTTCJ65uTn52P+/Pnw8vLCkSNHcPfuXdl6aWlp+P777+Hp6YmlS5eiqKjIxJGSMd24cUOvciIyLSaZRPRMSU9PR9euXREcHIyysjKd2pSUlGDFihXw8PDA1atXjRwhmYq3tzcsLVU/xmxsbNC9e3czRUREFTHJJKJnhhACkydPlp2psrbWfon5tWvXMHbsWOTn5xsjPDIxNzc3fPHFF6hTpw4AwNbWFqGhoWjSpImZIyMigEkmkdls3rwZx48fN3cYatXE+NavX68Sk7+/P3744QckJyejoKAA9+7dQ3R0ND799FO88MILsn1cu3YNc+fONVXIZGSzZ8/GnTt3EBcXh9u3b2PChAnmDomI/sIkk8gMnjx5go8++ggpKSnmDkVWTYwvKSkJixYtAgB069YNp06dQlRUFEaMGIGOHTvC2toajo6OeO2117B48WJcunQJAwcOlO0rJCQEP/zwgynDJyNydnbGq6++qvYPCyIyDyaZRGawa9cuPHjwwNxhqFUT49uxYwcKCwvh5uaGEydOwNvbW2P95s2bIyYmBhMnTpT9+datW40RJhER/YVJJpGJ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JCKqIRITE2WXejx16hT+9a9/AQDmzp2r11MCiIjMhXeXExGZ2c6dO7FixQqkpqbC2toanp6eGDx4MAYOHIgzZ85gxYoVKCkpgZOTE4KCgswdLhGRTphkEhGZUVFREebPn4+8vDwAQGlpKX777Tf89ttvWL58ubKepaUl/vOf/8De3t5coRIR6YVflxMRmdHZs2eVCaYm//nPf/DGG2+YICIiIsPgTCYREYAdO3Zg8+bNJh+3e/fuaNCggey1mMDTh/CvXr2az8UkomcOk0wiIgBZWVmIj483+bitWrXCl19+ibfeekuSaA4aNAgbN26Eq6uryeMiIqouJplERAA8PT0xc+ZMk4/r4eGBkSNHIjk5Gfv378etW7fQqVMn9OzZE926dTN5PEREhsIkk4gIwNChQzF06FCzjd+sWTPMmjXLbOMTERkab/whIiIiIoNjkklEREREBsckk4iIiIgMjkkmERERERkck0wiIiIiMjgmmURERERkcEwyiYiIiMjgmGQSERERkcExySQiIiIig+OKPwQAUCgUKtuZmZm4du2amaIhIqJnRWFhocq2EMJMkVBNwySTAABpaWkq24sXL8bixYvNFA0RET2rcnNzzR0C1RD8upyIiIiIDI5JJhEREREZHJNMIiIiIjI4XpNJAIBWrVqp3OizcuVKjBo1yowRERHRs2DcuHG4fPmyctvZ2Rl//PGHGSOimoJJJgEALC1VJ7WbNm2KTp06mSkaIiJ6VtSvX19l28LCwkyRUE3Dr8uJiIiIyOCYZBIRERGRwTHJJCIiIiKDY5JJRERERAbHJJOIiIiIDI5JJhEREREZHJNMIiIiIjI4JplEREREZHBMMomIiIjI4JhkEhEREZHBMckkIiIiIoNjkklEREREBsckk4iIiIgMjkkmERERERkck0wiIiIiMjgmmURERERkcEwyiYiIiMjgmGQSERERkcExySQiIiIig2OSSUREREQGxySTiIiIiAyOSSYRERERGRyTTCIiIiIyOCaZRERERGRw1uYOgIhIX9nZ2bh8+TLS09PRsWNHuLm5wd7e3txhERFRBUwyieiZEB8fj48++gjnz5/H3bt3JT9v1aoVPD098T//8z9wc3MzQ4RERFQRvy4nohotPz8f8+fPh5eXF44cOSKbYAJAWloavv/+e3h6emLp0qUoKioycaRERFQRk0wi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jRgxQlFRUapevbrL+7fZbJo/f76io6N17tw50+cHDhzQF198IUmqU6eOYmJi1L179yIcSelLTU3VyJEj9dlnn0mSKlWqpPr16+vIkSPatGmTvV/NmjVVp04dHT58WD179vRQtQDKAk6XAxXIQw89pIMHD2rGjBnq2LFjgX3Hjx+v5ORke8CUpHvuuUfHjh3ToEGDTP179eqlL774QosWLbK87uLKycnRkCFDFBERoZ07dyokJESTJ0/WN998o5SUFO3bt09vv/22qlWrJklKT0/X+PHjdf/99+vo0aMuzXH27Fm1a9dOQ4YM0YULFzR48GAtXLhQ+/bt09dff20KrMnJyXryySc1aNAgXb58uUSO2yonTpxQaGioPvvsM4WGhuqrr75SRkaGjh49qitXruj06dOaOHGiqlWrpkuXLunQoUMyDMPTZQPwNAMVUkBAgCHJ/hUaGuqwHRsb6+kSUQqGDx/u8Pd+/cvb29vIyMjId9zu3bsd+r///vulWLV70tLSjMcff9xea48ePYxLly457bt27VrT9+Lee+81zp49W+AcR48eNe655x5DkhESEmLEx8c77ZeYmGjUqlXLNEfnzp1dOpbAwECHcWFhYS6NK854m81mdOjQwZBk3HfffcbFixfz7ZuUlGTce++99v0//fTTbtWH8ik8PNzh5yoiIsJhOyoqytMlwjM+ZCUTqMCmT5+uyMhIU7vNZtP69evzHbd48WL7n6dOnaqRI0eWSH3FZbPZ1Lt3b/vp3E6dOmn16tX5Xgv51FNPqXnz5g5tiYmJevnll/Od49q1a+rRo4eOHTumwMBAbdmyRW3btnXa95577tGXX35pevPO119/rSVLlrhzaKVm9uzZ2rJli6TfLyMo6HrbunXravHixfLx8Smt8gCUYYRMoIL7+OOPFRISYmofOXKkrl69amrft2+fPvzwQ0nSgAED9Prrr5d4jUU1btw4e8D08vLSpEmTCn2MzyOPPGJq27Bhg7Kyspz2f+2113Tw4EFJ0syZM1WnTp0C9x8REaFp06aZ2keMGKGLFy8WONYTVq1aZf/z3XffXWj/Bx98UCNGjCjBigCUF4RMoIKrXr26li9frkqVKjm0nzp1SmPGjHFoy8nJ0YABA5STk6M2bdpo7ty5pVmqWw4cOKD33nvPvt2nTx/98Y9/LHRcixYtTG3p6ek6efKkqf3gwYOaN2+eJKlJkybq16+fS7X17dvX9Padc+fO6f3333dpfGn68ccf7X++HqYLM2TIkJIqB0A5QsgEoFatWmn8+PGm9jlz5mj79u327ejoaP34448KCgpSXFyc6bRvWfLOO+843HwyevRol8b16tXLdDr9gQce0H333Wfqe+OKZIcOHVyuLTAwUPXr1ze179mzx+V9lIbk5GSHR17NnDlTOTk5hY5r1KiRwsPDS7I0AOUAIROAJGnUqFGmU8WGYehvf/ubcnJytHnzZk2ZMkVeXl5asGCB05BUViQlJWnZsmX2bS8vL4WGhro0Njg4WD/88IMGDBigiIgI/f3vf9fy5ctN/a5du+ZwbWr79u3dqrFu3bqmtkOHDrm1j5J2c9g+evSonnnmGWVkZBQ69tlnny2psgCUEzwnE4AkydvbWwsWLFCLFi0crsXcv3+/xo4dqyVLlsgwDI0aNarMP9tx06ZNstls9u26devK39/f5fGNGjVSbGxsgX127dqlzMxM+/bLL7+s4cOHuzxHWlqaqe3UqVNKT093eGyUJwUEBKhu3br69ddf7W3r1q1T27ZtNX36dHXq1CnfscOHD9crr7xSGmUCKKMImQDsGjZsqOnTp+vFF190aL9+rWC7du0crnMsqzZv3uyw3ahRI8vn2LZtm2kOK97gc+7cOTVs2LDY+7HKs88+a7pWdP/+/Xr88cf15JNP6u2331br1q1N47y8vOTryz8xQEXGbwAADl544QWtWbNG//73vx3afX199fnnn5eL4PDzzz87bN91112Wz3HgwAGH7ejo6FvyDTdjxozR/Pnznd75vm7dOq1bt07du3dXdHS0HnzwQQ9UCKCs4ppMACYxMTG64447HNpyc3MdHmdTlt38fvWzZ89aPsfN74A/duyY5XOUBUFBQfruu+8KDOrr169Xu3bt1LlzZ+3bt68UqwNQlhEyAZjUrl1bM2bMMLWPHTvW4R3fZdXNIbMkAuDNK3u3asiUpGbNmik+Pr7QO+g3bdqksLAwDRs2zOk1pwAqFkImAKfWrFljasvKylL//v2VnZ3tgYpcZ9z03uxTp04pLy/P0jmuXbvmsH0rh0xJqlevnr799lvFxcUVeM1oXl6eZs2apbZt2yopKakUKwRQ1hAyAZjMmTNHy5cvV61atdSgQQOHz/bt26d//OMfHqrMNbfffrvDdm5urk6fPm3pHIGBgQ7biYmJlu6/rOrdu7cOHTqkiRMnFnijU0JCgjp06OBwZzqAioWQCcDBnj17NGLECHl5eWnx4sX67LPPTK9inDJlirZu3eqhCgt3cwCUnK/MuuPEiRMO2zcH2YSEBO3cubNYc9y8AltWVa5cWW+88YYSExP1+uuvm94WdV1iYqLeeOONUq4OQFlByARgl5aWpj//+c/KysrS2LFj1aVLFz366KMaNmyYQz+bzabnn39eV65c8VClBbv//vtNbTNmzCjyKfP//e9/pmeD3rzCK7n+ViFnMjMz9cADDyg5ObnI+yhtQUFBmjp1qhISEvJ9ZuaSJUtMd/sDqBgImQDsXnjhBSUmJqp9+/Z6++237e3vvfeeGjdu7ND3+PHjbj18vDRFRESY2o4fP64vv/yySPuLjo5Wnz59HNoefvhhU7/vvvtO69atK9Icn3zyiZKTk1W7du0ijS8JmZmZqlu3ri5dulRgv7vvvlv//e9/9e6778rHx8fhM8MwtGXLlpIsE0AZRcgEIEmaNWuWVq5cqVq1amnp0qUOYcHf318LFiwwBYjY2FitXr26tEst1M2vx7zugw8+cPuU9JdffqkdO3aob9++pjluvoxAkt58803l5ua6NUdmZqYmTZqkHj16yNu77PxaNgxDycnJLl1q4O3trTfffFNjxowxfVbWXpcJoHSUnd9mADzmhx9+0KhRo+zXYTp7r3abNm305ptvmtqHDBlSIs+hLI42bdqoRYsWpvb4+HhNnDjR5f0cPnxYAwcOVIsWLUwrubVq1XL6es39+/erb9++ysnJcWkOm82mfv366cyZM3r66addrq00rVixwuW+b731lurUqePQVrNmTatLAlAOEDKBCu7ChQv605/+pOzsbI0aNUpdunTJt+8//vEPNWvWzKHt/PnzGjBgQJm7aSW/U/nR0dH65JNPCh2/Z88ede/eXWlpafrrX//qtM+oUaOctq9cuVLdunXTqVOnCpwjOztbQ4cO1cqVK9W4cWN17ty5wP43B1d3V0yLOv7rr792+VpRPz8/hYWFObS1adPGtQIB3FIImUAFlpeXp7/85S86efKkWrVqpQkTJhTY38/PT/PmzTOd0t2wYYMmT55ckqW6rX///qawI/2+cjh48GD169fP6YPlz549q1deeUXh4eE6duyYHnnkEdONT9d1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N3Tr1g3dunWzuE+vXr3Qq1cvO1ZFRGQf/OIPEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1rloXQFVDTk6OZHnhwoX46KOPNKqGiIiqC51OJ1nOy8vTqBKqahgyCQBw//59yXJ6ejrS09M1qoaIiKqrgoICrUugKoKny4mIiIhIdQyZRERERKQ6ni4nAEC9evXw8OFDw/JTTz0Fb29vDSsiIqLqQKfTSd4/PDw8NKyGqhKGTAIA+Pj44NatW4bl999/H5GRkdoVRERE1UJISAjOnTtnWPby8tKwGqpKeLqciIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIda5aF0BE1Udubi4KCwstXt/Lywuenp42b8/V1RU+Pj6y9tTUVGzatAn79u1DeHg4Fi1aJFvn6tWrOHjwIH788Uf89NNP8PPzQ0BAACZOnIigoCCTdZeWliI+Ph6xsbG4du0a7t69ixYtWqBHjx6YOnUqGjZsaLK/JY4ePYrExETcuHED169fR1paGurWrQt/f380btwYAwcOxNChQ1GnTh2j2ygpKcGdO3esGtfPzw9OTk4AgLKyMvz+++8m169duzYaNGhg1RhERAAAQTWSh4eHAGC4dejQQbK8ZcsWrUukKmj8+PGS54mpm5OTk4iOjq7U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lsLPzw/PP/+81uUQEZEDMWQSkV3k5eVh7dq1AIDXXnvNoms2EhHRk4Mhk4jsYs2aNbhz5w48PT0xY8YMrcshIiIHY8gkIpulpKQoXofx5MmT+N///V8AwKxZsyTfiCYiopqB3y4nIqt99tlneP/993H9+nW4uroiODgYgwcPxrPPPovTp0/j/fffR0lJCXx8fBAVFaV1uUREpAGGTCKySlFREebMmYOcnBwAj35+8OzZszh79iyio6MN6zk7O+OTTz5BvXr1tCqViIg0xNPlRGSVM2fOGAKmKZ988glefPFFB1RERERVEUMmEVklMDAQXl5eRu93c3PDypUreV1MIqIajiGTiKzi7e2N7du3KwbNAQMGIDk5GbNmzdKgMiIiqkr4mUwistqoUaNw5coV7N27Fzdv3kT79u3RvXt3dO7cWevSiIioimDIJCKbNGnSBNOmTdO6DCIiqqJ4upyIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESq489KEgDgt99+kyxPnjwZU6dO1agaIiKqLoqLiyXLd+7c0agSqmoYMgkAUFJSIlkuLS1FaWmpRtUQEVF1xfcOKsfT5URERESkOoZMIiIiIlIdT5cTAMDPzw+pqamG5cGDB6NDhw4aVkRERNXBrl27kJWVZViuX7++htVQVcKQSQAAT09PyfL48eMRGRmpTTFERFRtnDhxQhIya9eurWE1VJXwdDkRERERqY4hk4iIiIhUx5BJRERERKpjyCQiIiIi1TFkEhEREZHqGDKJiIiISHUMmURERESkOoZMIiIiIlIdQyYRERERqY4hk4iIiIhUx5BJRERERKpjyCQiIiIi1TFkEhEREZHqGDKJiIiISHUMmURERESkOoZMIiIiIlIdQyYRERERqY4hk4iIiIhUx5BJRERERKpjyCQiIiIi1TFkEhEREZHqGDKJiIiISHUMmURERESkOletCyACgPj4ePzyyy8IDg5G//79tS7HLC3rrS6PVUZGBnbt2oW6detiypQpWpdjN9VlPoiIHI0hkzQ3duxY7Nmzx7A8c+ZMfPzxxxpWZJqW9Vb1x0oIgWPHjmHDhg2Ii4tDaWkpunXr9sSGzKo+H0REWuLpctLUsWPHJG/SALBq1Spc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kderV8/U5sozxUtSUsh01/dWlcexa9euDo+VlKTvvvtOq1at0sCBA8s1liR98sknOnPmjNO1OgFUPUImcB1JTEzUwoULNW3aNHvbunXrHPYJCwtT3bp1KzR+UVFRpeqrKh999JGeeeYZRUVFafHixfL29tYNN9yge++91/K5nJ1JrMyZtJICqju+t6o+jt26dZOHh4cp9E6aNEn9+vWTl5fr/8nKzc3V9OnTNWjQoBp7PS1Q2/E3D6ghSgoTZT2q0VU2m00jRoxQXl6ehgwZYm9PTk522C8tLc10l6+ramLIXL58uZ555hkZhqFJkyZV+tGWZQkICDC1nTp1yqW+qampprarH7N4ter+3qrjOIaFhTk9G7t//37Fxsa6fEbYZrNp+PDhSklJcfizDqB6ETKBGiIzM7Nc7eX16quv6vvvv9ddd93lcH1c8Tt6bTabtm7dWupYubm52rJli6k9OzvbYfvChQumfsUVFhaWUbljbVcrK3SkpqZq+PDh9jNjzq75s1pERISpLSEhocx+iYmJ6tKli6m9+DG8ojq/t+o8juPGjXPavnTpUvXv39+0uHtx+fn5GjNmjJYuXarbbrtN0dHRVVEmABcQMoEa4sodwsUdPXq0UuMahqEJEybojTfekCQ99NBDDu9HRkaa+rzwwgslnkGNj4/X3Xffrc8++8z03tVn137++WfT2orFn+gimddGLE3xQFo8HBX30UcfKSsry7791FNPafny5Tp9+nS5wm15tG/f3nTzT1xcXKnL8WzatEldunRx+nzto0ePOqw1eUV1fm/VeRx79OhR4qMkN23apNtvv10TJkzQ3r17HX5WP3XqlObOnavWrVtr7ty58vLyUlxcnHx8fCytD4DrCJlADREfH++0ff78+SUG0NLYbDZ988036tSpk2bMmGFvLx4yO3ToYOp76NAh9ejRw74Gos1mU3x8vMaOHavOnTvr9OnTGjZsmKnfwoULtX//fi1atEg9e/ZUmzZtHN5PSUkx9XH2bOuSXH2ji/Sfn3lLu95xz549DtsJCQkaMmSIbrzxRnl7e8vPz08hISFq0qSJWrVqpcjISN1zzz3q3r27HnzwQQ0bNkwvvviiPv/8c6dBz5nAwEB17NjRoa2goEBDhgzRjh07HNq2bNmiJ598Ug888IDuvPNOrVixwjReTk6Opk6dquzsbB0/flybNm2SVL3fW3Ufx/nz56tZs2ZO38vKytKMGTPUrl07+fr6qnnz5vL391fTpk01evRo+5nOSZMmOT1GAKqRcQ1LS0szJDm8QkNDHbY3b97s7jKBMu3bt88IDg42/Xm+8mrRooUxcuRIY8qUKcbrr79uTJs2zXjnnXfsrzfffNOYPHmyMXbsWOPpp582unbtatSvX980zs0332yaOycnx2jVqlWJc0dERBgNGza0b0dFRRnHjh0zNm/eXGIfScaAAQOMoqIih7nat29v2u+WW24xMjMzyzxGS5YscTrP3//+9xL7fPTRR6XWWJ5XnTp1jEGDBhlnzpwps9avv/66xHFuuukmo1u3boa/v7+9rXfv3sbly5cNm81WYj9PT09DktGhQwejqKioWr83dxzHPXv2GOHh4RWao3PnzkZ+fn6Z3xPgTjExMQ5/bnv06OGwHRMT4+4SK+tDQibgBllZWca5c+eMH374wZg5c6YRGBho2X/ES3u9+OKLTuvZsGGD4ePjU2pfDw8PY+zYsUZeXp5hGIaRkZFh1KtXz+m+AwcONLKysgybzWZcvHjR+PHHH41hw4aVOPb9999vbNu2zUhNTTVyc3PtdeXk5BjJycnGkiVLSgwc3t7exocffmgcP37cuHTpkuk49+rVy9JjGBoaauzcubPU79dmsxkjRoxwabzhw4cbly9ftvdztk/dunWNYcOGGdu2bauW783Zn1d3HMfExEQjMjKyXONGR0eb/hwANREhs4YjZOJa1aZNG0v/g+3qq3hIudqePXuMO+64w2lIufvuu41///vfpj5vvvmmw7716tUzpk6dahQWFhqGYRjbtm0rd42PPPKIffw6deqUq2+jRo1MNRYWFhpTp0619DjeeuutRk5OTqnfcWFhoTFmzJgSP0NAQIAxb948hz7FQ2aLFi2MadOmGWlpadX6vZX0edxxHPPy8oy33nqrzP8jFhwcbHzwwQdGQUFBqeMBNcX1EDI9DKMGr55chvT0dDVq1MihLTQ01OGan82bN6tnz57VXBlwbSooKNCePXuUkJCgtLQ03XPPPerYsaOCgoJK7LN9+3Z9//33Cg8PV3R0tMLDw6ux4rLl5+dr0KBB+u677+x1njlzRmfPntXZs2d18eJFZWdnKysrS9nZ2fb/ff78efsrOTnZdEPNrFmzNGbMmDLn379/v+Li4nT48GFl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oqOjS5xv586dJu1fQUGBZK6kpCSj52nXrp1m/Oeff270+Pfee08zvnv37kaP13Uk+Nq1a6Jt27ay4dLHx0f897//lYxZtGiRVl8HBwcxf/582deqqNjYWNGxY0ehUqk0Y62srMTUqVN1BmGiqqgqhEyVEK/uFXZTUlLg6uqq1ebi4qL1xf6wsDD06NGjnCsjImPcuXMH+/fvR0xMDOLj45GZmYkmTZqgWbNm8PDwwKBBg2TPXibD5efn49q1a7h69SqSk5PRvn17dO7cWfaEnZfOnTuH33//HfXq1YO/vz/q1atn8HrJyck4efIknJyc4OPjI/ldTVTVjRgxAnv37tVs+/n5aV39ITAwEHv27DFHaUpZa2XuCoiIWrZsiS+++MLcZVRq1tbW6NChAzp06GDwGF9fX/j6+pq0nqurK0aOHGnSWCKqHHh2OREREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcQyZRERERKQ4hkwiIiIiUhxDJhEREREpjiGTiIiIiBTHkElEREREimPIJCIiIiLFMWQSERERkeIYMomIiIhIcVbmLkBpmZmZWtujRo2Cvb29maohIiIikkpKStLaTk1NNVMlZafShcy8vDyt7cePH5upEiIiIiLDvHjxwtwlKI4flxMRERGR4hgyiYiIiEhxle7jcjs7O2RlZWm2W7ZsCUdHRzNWRERERKTt3r17ePLkiWa7Zs2aZqymbFS6kGlvb68VMr/77jv06NHDfAURERERFTNixAjs3btXs+3s7GzGasoGPy4nIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFMeQSURERESKY8gkIiIiIsUxZBIRERGR4hgyiYiIiEhxDJlEREREpDiGTCIiIiJSHEMmERERESmOIZOIiIiIFGdl7gKIiMpSRkYGdu7ciZSUFMyfP7/M1xNCYP/+/bhz5w7at2xf1nUAACAASURBVG+Pfv36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6623RExMjMjKyip3fkQ1HUNmDceQSXVRZVaH7n9V5OjRoxWG19atW4s9e/bo29wfMh0cHMTUqVNFbGysUZ+nsLBQLFmyRDg4OJQ7plarFStXrjQYWI8ePWryz2LMmDH69hYWFia1dXFxUZxHQkKC6NatmySsW1lZiWnTponMzExZ/ZYtW5o0bo8ePfRtX331VdV+F6oyj+r+brdv316pz2fK6i1RTVMfQqZGiNr7XK+MjAy4uLhIypycnCQXiEdGRsLf3/8Bz4yo5jt+/Dj27t2Ly5cvIy0tDVqtFm3btsXw4cMxcOBAWFlZ6euGhobi/PnzePbZZxEcHKy4LVJFCgoKcODAARw8eBApKSnIyMiAVqtFq1atMGjQIAwZMkTxOsaaKj09Hb/++iscHR3RpUsX2d9F9Uld+26JHoSxY8ciIiJCf+zn54cjR47ojwMDA7Fr1y5zTE0tq60qrkNEddHjjz+Oxx9/3Ki6a9eurfJ4tra2GDlypMG7k2sbFxcXBAUFmXsaNUJd+26JSB28u5yIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDqGTCIiIiJSHUMmEREREamOIZOIiIiIVMeQSURERESqY8gkIiIiItUxZBIRERGR6hgyiYiIiEh1DJlEREREpDorc09AbUVFRZLjpUuXYuvWrWaaDREREZHcX3/9JTnOyckx00yqT50Lmbm5uZLjH3/80UwzISIiIjLOjRs3zD0F1fF0ORERERGpjiGTiIiIiFRX506XW1lZSa7LtLW1haWlpRlnRERERCRVWFiIkpIS/XGDBg3MOJvqUedCZuPGjZGZmak//umnn+Dv72++CRERERGVMXbsWEREROiPmzdvjn///deMM1IfT5cTERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1DJhERERGpjiGTiIiIiFTHkElEREREqmPIJCIiIiLVMWQSERERkeoYMomIiIhIdQyZRERERKQ6hkwiIiIiUh1D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", - "text/plain": [ - "" - ] - }, - "execution_count": 20, - "metadata": { - "image/png": { - "width": 200 - } - }, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "store.write(\"clover_er_diagram_no_schema.png\", \"ER_Diagram\")\n", "\n", @@ -1441,33 +380,10 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "e1104b9e-a3af-4e5e-a8d6-acae74c59b0e", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Created an instance of DSI with the Sqlite backend: diana_schema_tutorial.db\n", - "Successfully loaded the schema file: ./clover3d/schema2.json\n", - "Loaded ./clover3d/ into the tables: input, output, simulation, viz_files\n", - "Loaded clover3d/dublin_core_dc.xml into the tables: dublin_core_datacard\n" - ] - }, - { - "ename": "RuntimeError", - "evalue": "ERROR: Cannot call summary() on an empty backend. Please ensure there is data in it.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[25], line 14\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[38;5;66;03m# Read in Datacard (Tier 1)\u001b[39;00m\n\u001b[1;32m 12\u001b[0m schema_store\u001b[38;5;241m.\u001b[39mread(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mclover3d/dublin_core_dc.xml\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mDublinCoreDatacard\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m---> 14\u001b[0m \u001b[43mschema_store\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msummary\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 16\u001b[0m \u001b[38;5;66;03m# dsi.write(filename, writer)\u001b[39;00m\n\u001b[1;32m 17\u001b[0m schema_store\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mclover_er_diagram.png\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mER_Diagram\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "File \u001b[0;32m/Users/Shared/dev/dsi/dsi/dsi.py:1169\u001b[0m, in \u001b[0;36mDSI.summary\u001b[0;34m(self, table_name, collection)\u001b[0m\n\u001b[1;32m 1153\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1154\u001b[0m \u001b[38;5;124;03mPrints numerical metadata and (optionally) sample data from tables in the active backend.\u001b[39;00m\n\u001b[1;32m 1155\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1166\u001b[0m \u001b[38;5;124;03m If False (default), prints each table's name and numerical metadata to the console.\u001b[39;00m\n\u001b[1;32m 1167\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1168\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mt\u001b[38;5;241m.\u001b[39mvalid_backend(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmain_backend_obj, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmain_backend_obj\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__bases__\u001b[39m[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m):\n\u001b[0;32m-> 1169\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mERROR: Cannot call summary() on an empty backend. Please ensure there is data in it.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 1170\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mschema_read:\n\u001b[1;32m 1171\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mERROR: Cannot call summary() until all associated data is loaded after a complex schema\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[0;31mRuntimeError\u001b[0m: ERROR: Cannot call summary() on an empty backend. Please ensure there is data in it." - ] - } - ], + "outputs": [], "source": [ "# Create a new database where we will relate a complex schema\n", "schema_store = DSI(\"diana_schema_tutorial.db\")\n", From 4047ed1faae5c6c1e64b6ff8d7182afa0c9034af Mon Sep 17 00:00:00 2001 From: krhom Date: Wed, 22 Apr 2026 11:23:51 -0600 Subject: [PATCH 097/109] fix: printing issue, tested with summary, list, display, and search --- dsi/cli.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dsi/cli.py b/dsi/cli.py index 9ef3f5bd..c7da1858 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -102,6 +102,11 @@ def startup(self, backend="sqlite"): if os.path.exists(self.db_path): os.remove(self.db_path) + keywords = None + if backend.lower() == "ndp": + keywords = input("Enter NDP search keywords (e.g., climate, temperature): ").strip() + + keywords = None if backend.lower() == "ndp": keywords = input("Enter NDP search keywords (e.g., climate, temperature): ").strip() @@ -893,7 +898,7 @@ def main(): args = parser.parse_args() if args.backend.lower() not in ["sqlite", "duckdb", "ndp"]: - print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, and ndp") + print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, ndp") exit(1) print(" ", textwrap.dedent(fr""" _____ ___ From cb9df1e689ac20c15c56284fc5b95fc3db3e7dcc Mon Sep 17 00:00:00 2001 From: Vedant Iyer Date: Wed, 22 Apr 2026 12:31:18 -0600 Subject: [PATCH 098/109] added ndp to cli -b flag help --- dsi/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/cli.py b/dsi/cli.py index c7da1858..66d72c10 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -898,7 +898,7 @@ def main(): args = parser.parse_args() if args.backend.lower() not in ["sqlite", "duckdb", "ndp"]: - print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, ndp") + print("ERROR: Invalid backend input. Valid backends are: sqlite, duckdb, and ndp") exit(1) print(" ", textwrap.dedent(fr""" _____ ___ From e50024fb1f54e79571dfd23c19f4a4630adfe8bf Mon Sep 17 00:00:00 2001 From: Divya Banesh Date: Tue, 28 Apr 2026 14:25:46 -0600 Subject: [PATCH 099/109] Add OSTI backend --- dsi/cli.py | 6 ++++++ dsi/dsi.py | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/dsi/cli.py b/dsi/cli.py index 66d72c10..860dab1f 100644 --- a/dsi/cli.py +++ b/dsi/cli.py @@ -129,6 +129,12 @@ def startup(self, backend="sqlite"): print("Use Python API: t.load_module('backend', 'OSTI', 'back-read', params={...})") backend = "OSTI" self.exit_cli([]) + elif backend.lower() == "osti": + # PLACEHOLDER for future implement + # OSTI requires params dict for initialization + print("OSTI backend requires configuration.") + print("Use Python API: t.load_module('backend', 'OSTI', 'back-read', params={...})") + self.exit_cli([]) else: backend = "Sqlite" self.t.load_module('backend','Sqlite','back-write', filename = self.db_path) diff --git a/dsi/dsi.py b/dsi/dsi.py index f32316ee..e48e4bc1 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -57,8 +57,8 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) self.silence_messages = kwargs.pop('silence_messages', False) - # Skip file creation checks for NDP and OSTI (read-only backends) - if backend_name.lower() not in ["ndp", "osti"]: + # Skip file creation checks for NDP (read-only backend) + if backend_name.lower() != "ndp": if "/" in filename: create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) else: @@ -68,7 +68,18 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if filename == ".temp_dsi.db" and os.path.exists(filename): os.remove(filename) - + # Skip file creation checks for OSTI (read-only backend) + if backend_name.lower() != "osti": + if "/" in filename: + create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) + else: + create_bool = self.t.can_create_file_here() + if create_bool is False: + raise RuntimeError("Cannot initialize DSI due to write permissions in this directory. Please try elsewhere.") + + if filename == ".temp_dsi.db" and os.path.exists(filename): + os.remove(filename) + if backend_name.endswith(".py"): if not os.path.exists(backend_name): raise RuntimeError("backend() ERROR: `backend_name` must be a valid filepath to the custom backend. Please check again.") @@ -190,6 +201,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if e.args: e.args = (f'backend ERROR: {str(e.args[0])}',) + e.args[1:] raise + raise # Handle file-based backends (Sqlite, DuckDB) else: @@ -244,7 +256,7 @@ def list_backends(self): print("DuckDB : In-process SQL backend optimized for fast analytics on large datasets.") n = NDP() if n.validate_connection(): - print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.") + print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.\n") n = OSTI() if n.validate_connection(): print("OSTI : Read-only data catalog backend for discovering and querying OSTI (REST-based) open data resources.\n") From 5bd7bb3f04588c0e2e312a49db7376750cf39e13 Mon Sep 17 00:00:00 2001 From: Jesus Pulido Date: Tue, 28 Apr 2026 15:54:27 -0600 Subject: [PATCH 100/109] fixed print spacing --- dsi/dsi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index e48e4bc1..8b4384a4 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -256,7 +256,7 @@ def list_backends(self): print("DuckDB : In-process SQL backend optimized for fast analytics on large datasets.") n = NDP() if n.validate_connection(): - print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.\n") + print("NDP : Read-only data catalog backend for discovering and querying NDP (CKAN-based) open data resources.") n = OSTI() if n.validate_connection(): print("OSTI : Read-only data catalog backend for discovering and querying OSTI (REST-based) open data resources.\n") From d1087f68e99699bed75100e34ae3e2eccb987159 Mon Sep 17 00:00:00 2001 From: Vedant1 Date: Tue, 28 Apr 2026 16:20:28 -0600 Subject: [PATCH 101/109] updated file creation skip for osti and ndp backends in user api --- dsi/dsi.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index 8b4384a4..eaff0b29 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -57,8 +57,8 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) self.silence_messages = kwargs.pop('silence_messages', False) - # Skip file creation checks for NDP (read-only backend) - if backend_name.lower() != "ndp": + # Skip file creation checks for NDP and OSTI (read-only backends) + if backend_name.lower() not in ["ndp", "osti"]: if "/" in filename: create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) else: @@ -68,18 +68,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if filename == ".temp_dsi.db" and os.path.exists(filename): os.remove(filename) - # Skip file creation checks for OSTI (read-only backend) - if backend_name.lower() != "osti": - if "/" in filename: - create_bool = self.t.can_create_file_here(filename.rsplit("/", 1)[0]) - else: - create_bool = self.t.can_create_file_here() - if create_bool is False: - raise RuntimeError("Cannot initialize DSI due to write permissions in this directory. Please try elsewhere.") - - if filename == ".temp_dsi.db" and os.path.exists(filename): - os.remove(filename) - + if backend_name.endswith(".py"): if not os.path.exists(backend_name): raise RuntimeError("backend() ERROR: `backend_name` must be a valid filepath to the custom backend. Please check again.") From 89baa2f52b919da51e97d3b8283410316505a5f9 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 11:59:34 -0600 Subject: [PATCH 102/109] VCS: fix linter errors --- dsi/utils/version_control/cli.py | 4 +-- dsi/utils/version_control/dsi_vcs.py | 11 +++--- .../version_control/vcs_metadata_helper.py | 34 ++++++++++++------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/dsi/utils/version_control/cli.py b/dsi/utils/version_control/cli.py index 50b801f4..06007da2 100644 --- a/dsi/utils/version_control/cli.py +++ b/dsi/utils/version_control/cli.py @@ -11,7 +11,7 @@ def main(): ) sub = parser.add_subparsers(dest="command", required=True) - p_init = sub.add_parser("init", help="Initialize a new repo") + sub.add_parser("init", help="Initialize a new repo") # p_init.add_argument("root_folder") p_add = sub.add_parser("add", help="add file or directory to staging") @@ -26,7 +26,7 @@ def main(): p_commit = sub.add_parser("commit", help="Snapshot and commit staged paths") p_commit.add_argument("message", nargs="?", default="Committed at " + datetime.datetime.now().isoformat()) - p_log = sub.add_parser("log", help="List all commits") + sub.add_parser("log", help="List all commits") p_diff = sub.add_parser("diff", help="Diff two commits") p_diff.add_argument("c1", nargs="?", default=None) diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 4c20c1a3..67ea3965 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -27,11 +27,10 @@ import json import datetime import shutil -from pathlib import Path from typing import Optional -from .vcs_db import * -from .vcs_metadata_helper import * +from .vcs_db import DB_NAME, SNAPSHOTS_DIR, open_db +from .vcs_metadata_helper import collect_metadata, collect_tree_metadata, owner_name # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -455,13 +454,13 @@ def get_files(chash): return result files1 = files2 = {} - if c1 == None and c2 == None: + if c1 is None and c2 is None: files1 = get_files_in_root_folder() files2 = get_files("latest") - elif c1 == None: + elif c1 is None: files1 = get_files_in_root_folder() files2 = get_files(c2) - elif c2 == None: + elif c2 is None: files1 = get_files(c1) files2 = get_files_in_root_folder() else: diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index ec39915c..8bc4da7c 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -1,7 +1,6 @@ import os import stat import hashlib -import sqlite3 import json import subprocess import pwd @@ -14,13 +13,20 @@ # ─────────────────────────── METADATA HELPERS ──────────────────────────────── def file_type_str(mode: int) -> str: - if stat.S_ISREG(mode): return "file" - if stat.S_ISDIR(mode): return "dir" - if stat.S_ISLNK(mode): return "symlink" - if stat.S_ISBLK(mode): return "block" - if stat.S_ISCHR(mode): return "char" - if stat.S_ISFIFO(mode): return "fifo" - if stat.S_ISSOCK(mode): return "socket" + if stat.S_ISREG(mode): + return "file" + if stat.S_ISDIR(mode): + return "dir" + if stat.S_ISLNK(mode): + return "symlink" + if stat.S_ISBLK(mode): + return "block" + if stat.S_ISCHR(mode): + return "char" + if stat.S_ISFIFO(mode): + return "fifo" + if stat.S_ISSOCK(mode): + return "socket" return "unknown" @@ -44,13 +50,17 @@ def permission_str(mode: int) -> str: def owner_name(uid: int) -> str: - try: return pwd.getpwuid(uid).pw_name - except KeyError: return str(uid) + try: + return pwd.getpwuid(uid).pw_name + except KeyError: + return str(uid) def group_name(gid: int) -> str: - try: return grp.getgrgid(gid).gr_name - except KeyError: return str(gid) + try: + return grp.getgrgid(gid).gr_name + except KeyError: + return str(gid) def get_acl(path: str) -> Optional[str]: From c393d594820598ade3a573023265948a1a255b69 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 12:13:30 -0600 Subject: [PATCH 103/109] VCS: fix version testers --- dsi/tests/test_core.py | 48 ------------------------------------------ dsi/tests/test_dsi.py | 35 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/dsi/tests/test_core.py b/dsi/tests/test_core.py index b7b72c95..67dbd134 100644 --- a/dsi/tests/test_core.py +++ b/dsi/tests/test_core.py @@ -21,54 +21,6 @@ def test_unload_module(): a.unload_module('plugin', 'GitInfo', 'writer') assert len(a.list_loaded_modules()['writer']) == 0 -def test_sqlite_versioning(tmp_path): - a = Terminal() - dbpath = tmp_path / "core_versioned.db" - # dbpath = '/tmp/versionined.db' - - a.load_module( - 'plugin', - 'Dict', - 'reader', - collection=OrderedDict({"wildfire": OrderedDict({'foo': [1], 'bar': [3]})}), - ) - a.load_module('backend', 'Sqlite', 'back-write', filename=str(dbpath)) - a.artifact_handler(interaction_type='ingest') - - a.vcs_init(tmp_path) - - # print(a.vcs_current_branch()) - # print(a.vcs_status()) - # status = a.vcs_status() - # assert status['branch'] == 'main' - # assert status['is_dirty'] is True - - # commit_id = a.vcs_commit('initial snapshot') - # assert len(commit_id) == 40 - # assert a.vcs_status()['is_dirty'] is False - # assert a.vcs_log(limit=1)[0]['message'] == 'initial snapshot' - # print(a.vcs_log()) - - # a.vcs_create_branch('feature', checkout=True) - # assert a.vcs_current_branch() == 'feature' - # print(a.vcs_list_branches()) - - # a.update_abstraction('wildfire', OrderedDict({'foo': [1, 2], 'bar': [3, 4]})) - # a.artifact_handler(interaction_type='ingest') - # diff = a.vcs_diff() - # print(diff) - # assert '+INSERT INTO \"wildfire\" VALUES(2,4);' in diff - - # a.vcs_commit('feature snapshot') - - # a.vcs_checkout_branch('main') - # table = a.get_table('wildfire') - # assert table.values.tolist() == [[1, 3]] - # assert sorted(a.vcs_list_branches()) == ['feature', 'main'] - - a.close() - - # SQLITE TESTS def test_ingest_sqlite_backend(): a = Terminal() diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 1ab996a9..faec7954 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -6,6 +6,8 @@ from pandas import DataFrame from collections import OrderedDict import hashlib +import random +import tempfile def test_list_functions(): test = DSI() @@ -1797,6 +1799,39 @@ def test_fail_overwrite_schema_duckdb_backend(): except Exception as e: expected = "schema() ERROR: A complex schema with a circular dependency cannot be ingested into a DuckDB backend." assert str(e) == expected + +def test_versioning(): + test = DSI() + wpath = os.getcwd() + test.version("init", wpath) + assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") + + # Create a file with ten random integers + dummy_file_path = os.path.join(wpath, "a_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "a_dummy_file")) + + dummy_file_path = os.path.join(wpath, "second_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "second_dummy_file")) + + dummy_file_path = os.path.join(wpath, "third_dummy_file") + with open(dummy_file_path, 'w') as f: + for _ in range(10): + f.write(str(random.randint(0, 100)) + '\n') + test.version("add", os.path.join(wpath, "third_dummy_file")) + + test.version("remove", os.path.join(wpath, "second_dummy_file")) + test.version("delete", os.path.join(wpath, "third_dummy_file")) + + test.version("commit", "Tester Commits") + test.version("log") + test.version("diff") + assert True # NDP From 70a62f0c30fb1f0f45d493afb754844c6e1d39a0 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 30 Apr 2026 12:15:02 -0600 Subject: [PATCH 104/109] VCS: remvoed unused import --- dsi/tests/test_dsi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index faec7954..0c0f9f29 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -7,7 +7,6 @@ from collections import OrderedDict import hashlib import random -import tempfile def test_list_functions(): test = DSI() From 36aa7874108c9e928749c2106c63bef38167fa37 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 14 May 2026 10:33:58 -0600 Subject: [PATCH 105/109] VCS: readme user text update --- dsi/dsi.py | 1 - dsi/utils/version_control/README.md | 32 ++++++++++++++--------------- dsi/utils/version_control/vcs_db.py | 2 +- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/dsi/dsi.py b/dsi/dsi.py index eaff0b29..9d1a3097 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -1285,7 +1285,6 @@ def version(self, command: str, args: str = None): - diff: An optional argument to specify the versions to compare, specified as a space-separated string or list of commit hashes. - restore: A required argument to specify the version to restore, specified by its commit hash. """ - print("hello from versioning") if command == "init": if args is None: raise RuntimeError("version() ERROR: 'init' command requires a 'root_folder' argument specifying the name of the root folder for the versioning repository.") diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index 8b5dd0ea..aa5ec8eb 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -89,22 +89,22 @@ Example output: ```shell Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) - -STATUS PATH + +STATUS PATH ────────────────────────────────────────────────────────────────────── -MODIFIED file_new [owner] -MODIFIED file_schema.json [owner] -diff result: 2c2 -< "genesis_datacard": { ---- -> 2"genesis_datacard": { -26c26 -< } -\ No newline at end of file ---- -> } -MODIFIED schema2.json [content, size] - +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + Summary: +0 added -0 deleted ~3 modified =4 unchanged ``` @@ -278,7 +278,7 @@ dsi.version("diff") | root_folder | TEXT | Repository root path | | commit_hash | TEXT | UUID4 hex (32 chars) | | committed_at | TEXT | ISO-8601 timestamp | -| owner_name | TEXT | Linux user who committed | +| owner_name | TEXT | Username of the committer | | message | TEXT | Optional commit message | | snapshot_path | TEXT | Path to rsync snapshot copy | | file_count | INTEGER | Number of files in commit | diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index c5e849fa..c7bed45c 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -14,7 +14,7 @@ root_folder TEXT NOT NULL, commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex committed_at TEXT NOT NULL, -- ISO-8601 timestamp - owner_name TEXT NOT NULL, -- Linux user who ran the commit + owner_name TEXT NOT NULL, -- Username of the committer message TEXT, snapshot_path TEXT NOT NULL, -- path to rsync copy file_count INTEGER NOT NULL, From 257f72943d8757975857744fe051975edda1e173 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 21 May 2026 10:45:55 -0600 Subject: [PATCH 106/109] VCS: remove redundant table columns --- dsi/utils/version_control/README.md | 68 +++++++++---------- dsi/utils/version_control/dsi_vcs.py | 7 +- dsi/utils/version_control/tests/test_vcs.py | 29 +------- dsi/utils/version_control/vcs_db.py | 8 +-- .../version_control/vcs_metadata_helper.py | 9 +-- 5 files changed, 40 insertions(+), 81 deletions(-) diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index aa5ec8eb..23e6c85f 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -89,22 +89,22 @@ Example output: ```shell Diff f826177ae78f4e48a8c08054e2bb9a71 → None (./root_folder) - -STATUS PATH + +STATUS PATH ────────────────────────────────────────────────────────────────────── -MODIFIED file_new [owner] -MODIFIED file_schema.json [owner] -diff result: 2c2 -< "genesis_datacard": { ---- -> 2"genesis_datacard": { -26c26 -< } -\ No newline at end of file ---- -> } -MODIFIED schema2.json [content, size] - +MODIFIED file_new [owner] +MODIFIED file_schema.json [owner] +diff result: 2c2 +< "genesis_datacard": { +--- +> 2"genesis_datacard": { +26c26 +< } +\ No newline at end of file +--- +> } +MODIFIED schema2.json [content, size] + Summary: +0 added -0 deleted ~3 modified =4 unchanged ``` @@ -288,26 +288,24 @@ dsi.version("diff") Stores metadata for each file in each commit. -| Column | Type | Description | -| ---------------------- | ---------- | -------------------------------- | -| id | INTEGER PK | Auto-increment | -| version_id | INTEGER FK | References versions(id) | -| root_folder | TEXT | Partition key | -| relative_path | TEXT | Path relative to root | -| absolute_path | TEXT | Full path | -| file_name | TEXT | Filename only | -| file_type | TEXT | file/dir/symlink/etc | -| md5_hash | TEXT | Content hash (files only) | -| lstat | TEXT | JSON of os.lstat() result | -| permissions_octal | TEXT | e.g., "0o755" | -| permissions_str | TEXT | e.g., "rwxr-xr-x" | -| owner_name | TEXT | Username | -| group_name | TEXT | Group name | -| setuid, setgid, sticky | INTEGER | Special bits (0/1) | -| acl_text | TEXT | Raw getfacl output | -| xattrs | TEXT | JSON dict of extended attributes | -| security_context | TEXT | SELinux context | -| symlink_target | TEXT | Target of symlink | +| Column | Type | Description | +| ---------------- | ---------- | -------------------------------- | +| id | INTEGER PK | Auto-increment | +| version_id | INTEGER FK | References versions(id) | +| root_folder | TEXT | Partition key | +| relative_path | TEXT | Path relative to root | +| absolute_path | TEXT | Full path | +| file_name | TEXT | Filename only | +| file_type | TEXT | file/dir/symlink/etc | +| md5_hash | TEXT | Content hash (files only) | +| lstat | TEXT | JSON of os.lstat() result | +| permissions_int | INTEGER | e.g. 755 | +| owner_name | TEXT | Username | +| group_name | TEXT | Group name | +| acl_text | TEXT | Raw getfacl output | +| xattrs | TEXT | JSON dict of extended attributes | +| security_context | TEXT | SELinux context | +| symlink_target | TEXT | Target of symlink | ### `staging` Table diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 67ea3965..15eede3e 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -351,8 +351,7 @@ def cmd_commit(self, message: str = ""): "version_id", "root_folder", "relative_path", "absolute_path", "file_name", "file_type", "md5_hash", "lstat", - "permissions_octal", "permissions_str", "owner_name", "group_name", - "setuid", "setgid", "sticky", + "permissions_int", "owner_name", "group_name", "acl_text", "xattrs", "security_context", "symlink_target", ] placeholders = ",".join("?" * len(cols)) @@ -439,7 +438,7 @@ def get_files(chash): conn.close() sys.exit(f"Commit '{chash}' not found.") rows = conn.execute( - "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_octal, " + "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_int, " " owner_name, group_name, lstat " "FROM file_entries WHERE version_id=?", (vid["id"],) ).fetchall() @@ -495,7 +494,7 @@ def get_files(chash): changes.append("content") result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) print(f"diff result: {result.stdout.strip()}") - if f1["permissions_octal"] != f2["permissions_octal"]: + if f1["permissions_int"] != f2["permissions_int"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: changes.append("owner") diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 6b7ba913..6a13c930 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -29,7 +29,7 @@ def latest_entries(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row return conn.execute( - "SELECT relative_path, file_type, permissions_octal, permissions_str, setgid " + "SELECT relative_path, file_type, permissions_int " "FROM file_entries WHERE version_id=(SELECT MAX(id) from versions) " "ORDER BY relative_path").fetchall() @@ -66,29 +66,4 @@ def test_add(tmp_path): rows = {row["relative_path"]: row for row in latest_entries(tmp_path)} empty_entry = rows["empty"] assert empty_entry["file_type"] == "dir" - assert empty_entry["permissions_str"] == "rwxrwsr-x" - assert eval(empty_entry["permissions_octal"]) == 0o2775 - assert empty_entry["setgid"] == 1 - - -def test_versioning(): - test = DSI() - # test.version("init", os.getcwd()) - # wpath = "/Users/ssakin/Public/versioning-test/clover-demo" - wpath = os.getcwd() - test.version("init", wpath) - assert os.path.exists(wpath + "/.dsi_vcs_snapshots/.dsi_vcs.db") - - test.version("add", os.path.join(wpath, "a_dummy_file")) - print(">Single file added.") - test.version("add", os.path.join(wpath, "schema.json") + " " + os.path.join(wpath, "schema2.json")) - print(">Multi file added.") - print(">Versioning initialized.") - - test.version("remove", os.path.join(wpath, "schema2.json")) - print(">Single file removed.") - - test.version("commit", "Tester Commits") - test.version("log") - test.version("diff") - assert True + assert empty_entry["permissions_int"] == 0o2775 diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index c7bed45c..989ce0da 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -42,16 +42,10 @@ lstat TEXT NOT NULL, -- ── Human-readable permission strings ───────────────────────────────── - permissions_octal TEXT, -- e.g. "0o755" - permissions_str TEXT, -- e.g. "rwxr-xr-x" + permissions_int INTEGER, -- e.g. "755" as an integer owner_name TEXT, group_name TEXT, - -- ── Special bits ────────────────────────────────────────────────────── - setuid INTEGER DEFAULT 0, - setgid INTEGER DEFAULT 0, - sticky INTEGER DEFAULT 0, - -- ── ACL (POSIX) ─────────────────────────────────────────────────────── acl_text TEXT, -- raw getfacl output diff --git a/dsi/utils/version_control/vcs_metadata_helper.py b/dsi/utils/version_control/vcs_metadata_helper.py index 8bc4da7c..6bf4a417 100644 --- a/dsi/utils/version_control/vcs_metadata_helper.py +++ b/dsi/utils/version_control/vcs_metadata_helper.py @@ -39,7 +39,6 @@ def permission_str(mode: int) -> str: (stat.S_IROTH, "r"), (stat.S_IWOTH, "w"), (stat.S_IXOTH, "x"), ): chars.append(char if mode & bit else "-") - # Replace x with s/t for setuid/setgid/sticky if mode & stat.S_ISUID: chars[2] = "s" if chars[2] == "x" else "S" if mode & stat.S_ISGID: @@ -219,16 +218,10 @@ def collect_metadata(abs_path: str, root_folder: str) -> dict: "lstat": json.dumps(lstat_dict), # human-readable permission strings - "permissions_octal": oct(stat.S_IMODE(mode)), - "permissions_str": permission_str(mode), + "permissions_int": stat.S_IMODE(mode), "owner_name": owner_name(s.st_uid), "group_name": group_name(s.st_gid), - # special-bit flags - "setuid": int(bool(mode & stat.S_ISUID)), - "setgid": int(bool(mode & stat.S_ISGID)), - "sticky": int(bool(mode & stat.S_ISVTX)), - # external / subprocess-derived attributes "acl_text": get_acl(abs_path), # "xattrs": get_xattrs(abs_path), From 89ec56710d0913db822a43aac4a9e6cd1c3a0585 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 15 Jul 2026 12:13:37 -0600 Subject: [PATCH 107/109] VCS: remove perf analyzer example --- examples/perf_analyzer/Readme.md | 32 - examples/perf_analyzer/auto_perf_check.sh | 64 - examples/perf_analyzer/code_sensing.py | 87 - .../perf_analyzer/create_and_query_dsi_db.py | 94 - .../db_backups/clover_perf_runner_test_run.db | Bin 16384 -> 0 bytes ...clover_perf_runner_test_run_large_input.db | Bin 16384 -> 0 bytes ...erf_runner_test_run_large_input_compact.db | Bin 16384 -> 0 bytes .../clover_perf_runner_test_run_legacy.db | Bin 20480 -> 0 bytes .../db_backups/clover_random_test.csv | 13 - .../db_backups/clover_random_test.db | Bin 8192 -> 0 bytes examples/perf_analyzer/environment.yml | 47 - examples/perf_analyzer/fly_server.py | 1041 ----------- .../initate_git_workflow_through_issue.py | 26 - examples/perf_analyzer/parse_clover_output.py | 278 --- .../perf_analyzer/parse_output_analyzer.py | 295 ---- examples/perf_analyzer/perf_hist_chart.ipynb | 1520 ----------------- examples/perf_analyzer/requirements.txt | 7 - examples/perf_analyzer/runner_script.sh | 28 - examples/perf_analyzer/sample_charting.py | 48 - 19 files changed, 3580 deletions(-) delete mode 100644 examples/perf_analyzer/Readme.md delete mode 100755 examples/perf_analyzer/auto_perf_check.sh delete mode 100644 examples/perf_analyzer/code_sensing.py delete mode 100644 examples/perf_analyzer/create_and_query_dsi_db.py delete mode 100644 examples/perf_analyzer/db_backups/clover_perf_runner_test_run.db delete mode 100644 examples/perf_analyzer/db_backups/clover_perf_runner_test_run_large_input.db delete mode 100644 examples/perf_analyzer/db_backups/clover_perf_runner_test_run_large_input_compact.db delete mode 100644 examples/perf_analyzer/db_backups/clover_perf_runner_test_run_legacy.db delete mode 100644 examples/perf_analyzer/db_backups/clover_random_test.csv delete mode 100644 examples/perf_analyzer/db_backups/clover_random_test.db delete mode 100644 examples/perf_analyzer/environment.yml delete mode 100755 examples/perf_analyzer/fly_server.py delete mode 100644 examples/perf_analyzer/initate_git_workflow_through_issue.py delete mode 100644 examples/perf_analyzer/parse_clover_output.py delete mode 100644 examples/perf_analyzer/parse_output_analyzer.py delete mode 100644 examples/perf_analyzer/perf_hist_chart.ipynb delete mode 100644 examples/perf_analyzer/requirements.txt delete mode 100644 examples/perf_analyzer/runner_script.sh delete mode 100644 examples/perf_analyzer/sample_charting.py diff --git a/examples/perf_analyzer/Readme.md b/examples/perf_analyzer/Readme.md deleted file mode 100644 index 9175e01f..00000000 --- a/examples/perf_analyzer/Readme.md +++ /dev/null @@ -1,32 +0,0 @@ -## PerfAnalyzer - -A tool to analze software performance along with code history. This is built on top of a DSI SQLite backend. - -Run `fly_server.py` file. Then the dashboard can be accessed through `http://127.0.0.1:8050/`. - -##### TODO: add a requirement file - -Update the `runner_script.sh` to compile, copy input file, and run the program. - -Update the `parse_clover_output.py` file and update `parse_clover_output_file` function to parse specific output file. Return a dictionary containing the contents of the parsed output file. - -#### The features available in the dashboard - -PerfAnalyzer is a dashboard based visualizer to analyze performance using git commit history and different performance metric. This has the following features - -- Git History Graph - - Ordered by commit dates - - Filter Git Branch - - Select a subset of git commits - - Show Commit details like message, committer name, date, and hash. -- Performance metric line chart - - Filter by different metric - - Show details on hover - - Commit table - - Search and filter by date, hash, and message. - - Execute the `runner_script` on selected commit. - - Show difference between two commits (using git diff) - - Variable Search - - Use any regex or string to search - - Show table of found variable and file - - Show file content \ No newline at end of file diff --git a/examples/perf_analyzer/auto_perf_check.sh b/examples/perf_analyzer/auto_perf_check.sh deleted file mode 100755 index 4c01f2f1..00000000 --- a/examples/perf_analyzer/auto_perf_check.sh +++ /dev/null @@ -1,64 +0,0 @@ -#! /bin/bash - -# make sure that cdsi environment is activated -if [[ $CONDA_DEFAULT_ENV != 'cdsi' ]]; then - echo "activate conda cdsi environment." - exit 0 -fi -if [ -z ${SOURCE_BASE_DIRECTORY+x} ]; then - echo "SOURCE_BASE_DIRECTORY is unset" - exit 0; -else - echo "SOURCE_BASE_DIRECTORY is set to '$SOURCE_BASE_DIRECTORY'"; -fi - -# SOURCE_BASE_DIRECTORY="/Users/ssakin/projects/CloverLeaf/CloverLeaf_ref" -MPI_THREADS=4 -export CHECK_PREV_COMMITS=15 -export OMP_NUM_THREADS=4 -base_directory=$(pwd) - -run_and_check_commit() { - echo "current commit hash $1" - - cd $SOURCE_BASE_DIRECTORY - git checkout $1 - make clean - make COMPILER=GNU - echo "================================ Compile Done ================================ " - - echo "============================= Running CloverLeaf ============================= " - mpirun -np $MPI_THREADS clover_leaf - cp clover.out $base_directory"/clover_output/clover_$1.out" - echo "======================= CloverLeaf Executed for has $1 ======================= " - - echo "=========================== Running output parser ============================ " - cd $base_directory - python3 parse_clover_output.py --testname random_test --gitdir $SOURCE_BASE_DIRECTORY - echo "============================ Output CSV updated ============================== " -} - -track_variables() { - echo "current commit hash $1" - - cd $SOURCE_BASE_DIRECTORY - git checkout $1 - - echo "=========================== Running code sensing ============================ " - cd $base_directory - python3 code_sensing.py --testname random_test --gitdir $SOURCE_BASE_DIRECTORY - echo "============================ Output CSV updated ============================== " -} - -cd $SOURCE_BASE_DIRECTORY -prev_hash=( $(git log master -n "$CHECK_PREV_COMMITS" --format=format:%h) ) - -for c_hash in "${prev_hash[@]}" -do -# run_and_check_commit $c_hash - track_variables $c_hash -done - -cd $SOURCE_BASE_DIRECTORY -git checkout master -echo "=========================== Auto Perf Script Completed ============================ " diff --git a/examples/perf_analyzer/code_sensing.py b/examples/perf_analyzer/code_sensing.py deleted file mode 100644 index 1455da73..00000000 --- a/examples/perf_analyzer/code_sensing.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import sys -import glob -import re - -def recursive_c_directive_match(re_list, search_file_list, cur_dir): - """ The data is parsed from all of the files in the current directory """ - occurrence = dict() - for code_files in glob.iglob('**', root_dir=cur_dir, recursive=True): - for f_type in search_file_list: - if re.search(f_type + '$', code_files): - with open(cur_dir + "/" + code_files, 'r') as cf: - line_number = 1 - for line in cf: - for each_re in re_list: - line_match = re.compile(r"\s*[#]" + each_re + r"\s+(\w+)[\t\s]+(.*)\s*(\r\n|\r|\n)").match(line) - if line_match is not None: - c_line = "#" + each_re + " " + line_match.group(1) - second_part = line_match.group(2) - if second_part is not None and len(second_part) > 0: - c_line = c_line + " " + line_match.group(2) - c_line = c_line.rstrip() - occurrence[c_line] = occurrence.get(c_line, dict()) - # occurrence[line]["first"] = line_match.group(1) - # occurrence[line]["second"] = line_match.group(2) - occurrence[c_line][code_files] = occurrence[c_line].get(code_files, list()) - occurrence[c_line][code_files].append(line_number) - line_number = line_number + 1 - print("matching done") - return occurrence - -def recursive_customized_match(re_list, search_file_list, cur_dir): - """ The data is parsed from all of the files in the current directory """ - occurrence = dict() - for code_files in glob.iglob('**', root_dir=cur_dir, recursive=True): - for f_type in search_file_list: - if re.search(f_type + '$', code_files): - with open(cur_dir + "/" + code_files, 'r') as cf: - line_number = 1 - for line in cf: - for each_re in re_list: - line_match = re.compile(r".*(" + each_re + r").*").match(line) - if line_match is not None: - # c_line = line_match.group(1) - c_line = line.rstrip() - occurrence[c_line] = occurrence.get(c_line, dict()) - # occurrence[line]["first"] = line_match.group(1) - # occurrence[line]["second"] = line_match.group(2) - occurrence[c_line][code_files] = occurrence[c_line].get(code_files, list()) - occurrence[c_line][code_files].append(line_number) - line_number = line_number + 1 - print("matching done") - return occurrence - -def main(): - """ A testname argument is required """ - parser = argparse.ArgumentParser() - parser.add_argument('--testname', help='the test name') - parser.add_argument('--gitdir', help='the git directory') - args = parser.parse_args() - # testname = "temp_test" - testname = args.testname - git_repo = args.gitdir - if testname is None or git_repo is None: - parser.print_help() - sys.exit(0) - - re_list = ["pragma", "define"] - search_file_list = [r"\.c", r"\.cc"] - # occ = recursive_c_directive_match(re_list, search_file_list, git_repo) - - # with open(git_hash + '.pickle', 'wb') as handle: - # pickle.dump(occ, handle, protocol=pickle.HIGHEST_PROTOCOL) - - # re_list = [r"OMP PARALLEL", r"vol=0\.0", r"\w+=\d+\.\d+"] - search_file_list = [r"\.c", r"\.cc", r"\.f90"] - recursive_customized_match(re_list, search_file_list, git_repo) - - # with open(git_hash + '.pickle', 'rb') as handle: - # b = pickle.load(handle) - # print(b) - -if __name__ == '__main__': - main() - diff --git a/examples/perf_analyzer/create_and_query_dsi_db.py b/examples/perf_analyzer/create_and_query_dsi_db.py deleted file mode 100644 index 62763024..00000000 --- a/examples/perf_analyzer/create_and_query_dsi_db.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 - -""" -This script reads in the csv file created from parse_slurm_output.py. -Then it creates a DSI db from the csv file and performs a query. -""" - -# from dsi.backends.sqlite import Sqlite, DataType -# from dsi.plugins.collection_reader import Dict - -from dsi.dsi import DSI -isVerbose = True - - -def test_dsi_dict(): - testDict = {"one":1, "two": "number two", "three":"three" } - - store = DSI() - store.read(testDict, "Collection", table_name="test_dsi") - store.get_table("test_dsi") - store.summary() - - testDict2 = {"one":15, "three":4, "four":44 } - store.read(testDict2, "Collection", table_name="test_dsi") - store.get_table("test_dsi") - store.summary() - - store.close() - # dsi_dict = Dict(testDict) - # print("--") - # print(dsi_dict.collections) - # print(dsi_dict.base_dict) - # print(dsi_dict.output_collector) - # # dsi_dict.add_rows() - # print("==>") - # print(dsi_dict.collections) - # print(dsi_dict.base_dict) - # print(dsi_dict.output_collector) - # # dsi_dict.collections.clear() - # dsi_dict.collections.append({"one":15, "three":4, "four":44 }) - # # dsi_dict.base_dict["four"] = "fort minor" - # dsi_dict.add_rows() - # print("~~>") - # print(dsi_dict.collections) - # print(dsi_dict.base_dict) - # print(dsi_dict.output_collector) - -""" -Creates the DSI db from the csv file -""" - -def import_cloverleaf_data(test_name): - csvpath = 'clover_' + test_name + '.csv' - dbpath = 'clover_' + test_name + '.db' - - store = DSI(dbpath) - store.read(csvpath,"CSV", table_name="rundata") - # store = Sqlite(dbpath) - # store.put_artifacts_csv(csvpath, "rundata", isVerbose=isVerbose) - store.close() - # No error implies success - -""" -Performs a sample query on the DSI db -""" -def test_artifact_query(test_name): - dbpath = "clover_" + test_name + ".db" - store = DSI(dbpath) - data_name = "rundata" - query_result = store.query(f"SELECT * FROM {data_name} WHERE Viscosity > 0.1", True) - query_result.to_csv("clover_query.csv", index=False) - - # store = Sqlite(dbpath) - # _ = store.get_artifact_list(isVerbose=isVerbose) - # data_type = DataType() - # data_type.name = "rundata" - # query = "SELECT * FROM " + str(data_type.name) + " WHERE Viscosity > 0.1" - # print("Running Query", query) - # store.export_csv_query(query, "clover_query.csv") - store.close() - -if __name__ == "__main__": - # """ The testname argument is required """ - # parser = argparse.ArgumentParser() - # parser.add_argument('--testname', help='the test name') - # args = parser.parse_args() - # test_name = args.testname - # if test_name is None: - # parser.print_help() - # sys.exit(0) - - # # import_cloverleaf_data(test_name) - # test_artifact_query(test_name) - test_dsi_dict() diff --git a/examples/perf_analyzer/db_backups/clover_perf_runner_test_run.db b/examples/perf_analyzer/db_backups/clover_perf_runner_test_run.db deleted file mode 100644 index 4756b514fd0d377664745b40f842c68d16c3f659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 ciW{sH)kc2`$db)VDsS-<|(^ZqQgtMOzQ zW^LK}L2GNP^|;+`wOSAHe;facZ3_q2&llI;_WQ8aIs3~4T-~~V(Zb$r4FU}U4FU}U z4FU}U4FU}U4FU}U-){u|{MFX`2OodD_1EKBCY{%JFd6pUu)_^)&Wkn9Vy+((7@rzV|Fmrv35g zu>D1tzUm#1Pe(HxpG;DSi?j1ynFfRDVf)SE;C$G}9dEKcABJ-}CfOhLXZMW!*j;57TINI$Um>q-nwc)6?NFOfW2F=;M6WA0$9I z828e7c@jo1@yydSsIIpSU-z#!&Bn7Zzz8R)hruz{i(fwL{WkQ`I_Rb0G?lAO{i~)Y zgTW-l(=sjhyJ}qO`3wLrcgp8W*MHU@g+c$Tm4PnXmim4EU(?N7mil}aCX;@eEKj#+ zTI%&|O582IU)`|WW0K0Vl^(fasRv&5WdLw1b6qtq^*qM6-J4!}!K=pWJttxGYI)|| zGdJd*Ke+?jv$gYYvOu@%pYOLZYTmv7r96#l*NMov&<> zl;+mRAVn-f(5VpRXpAc0;LT$c4^K{u4yuNN33kNn5tnVjyWDi8`pwUeUOYMehpm@QSjBN-E1NT(S6vkvWi{*NEUJDPr2M{8$U$2u)o$1sl;W0?{i=D1`X=_UGk zDe0;cR-lh!3N4CSgvGtgF!W@!k;JiO23M`(jO&gQo~B|L)JfWfP|7Id9P4mqX%?+a z76Nx^O${XQ0uYL3V5}ZuMPBTFcr#)^N=*!rvyK>9Ip>wvvGA%iRS`6ZD0$_*w3YJ7 z6~$tupu;FyYKQo$;hWGN(9T4`Z?8Q-ofOtzWtD%TZ$ zL!1~xoNxy)jOD}$zGrX(V8Ds6q(ry~PK?n|7LnOg))Oa&ByA+YhE)VFq$W0&12y;hqc5GZ`F%Xc908q!ctgTM)s%{?q$l;gfG~vWSG8{r!V|;$j^O%tq^E z2|JKv02c);l~vM86NP2CC15bhr#2M@oX6SgSldeBQB1FYID^Urpw%hNU#P- z8Q3^Tg9if$3dbs&4KK6WIG9&_v{r!x%Yl(m86;)Nw5*Vz0VcTgnS>fi(jQGb_eKev zU_!E~a}F?BHdC1fT@#8nkb1HG-v597V+#^0btar(f4KcVe3DS`Ly%3~0S3{BVZ7c`nN3iw-- zAtjHY^Nz0`pW56oNk7M5k@q42F9RVlS*=ub29I&^;LgohSlA-C?e$s^lLGuv2c<)$ zOQ-RXg^FA5Tq&y3Mp-(^i|QRLz&+?q`E4V}*x)%Jfir0V85PB|-{xe+Yrn0%Ar^G@ z^O6h_gJNW)u1t|26J?aoEGSz8DTU&+bFU;6y*5R7^_m7nLuJ7@@z3Pk-OpK|8M3nR ziwC#C$4_rymB=T2lT{L_iZKZj3Z7DBY^=(pgYewQ4js$DNZ41`bdQ;ZPl3s}XReR>aOSV6Xk$v}wzi<^6jN&OLh!?Wp zMj%!LFJ#le=oK$O9ajx(7+eI?)QPmi!1cjJaHX88d0`sEAdJi+dkXZ;{pkd*B9A%e zy+S+Z2st{a|?Cxu+%qa>P`t2UIH$w?7xDBF`$>5@AAr)$E@KiHW2Ty);j9But1~CI9+&5V)4{Q~;z?mW(v|ERE z#Iv3>gW6%{vc;NnvBZd^4_@OoFai!Twg^Qm?LwGKM&Q~h!UY)u^Jg(PSUOWt>%=lO z7q_-R$I-X8R|uZ4)%vkMmgU+dAtQ5@l~J0?@-oQ`%?n*Fi{%cCw>H2Ex#ec817MbkzWJ0EKup!*xXl zv>RkFQLD#g5H(j1fiMAv0K5Z-ITwt$Ai;R{HrKU-QU1SukGCFvdHpp}YAq`8>yt?5%hGdS2v-@0%uz?l3Z-JKw8DA- zj{*5x$=_9F!KxK%z(`+ASD6h}S&$(je?VOdNs0DIQ;<*MxGoi9b%Szw$EgOXQ7J@r z?{fM={TUU_R8e0Q44U-K<;ev%Ty}-nkm6WRtf2S+iJ}(r zepF4VECx7{_ftUz3ZpJi5sv1xKV;3#X(JykR`=ov|}al ciW{sHU^jA*T1?m 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 diff --git a/examples/perf_analyzer/db_backups/clover_random_test.csv b/examples/perf_analyzer/db_backups/clover_random_test.csv deleted file mode 100644 index 34de1ff5..00000000 --- a/examples/perf_analyzer/db_backups/clover_random_test.csv +++ /dev/null @@ -1,13 +0,0 @@ -testname,git_hash,git_committer,git_committed_date,git_repo_name,version,Task Count,Thread Count,x_cells,y_cells,xmin,ymin,xmax,ymax,initial_timestep,timestep_rise,max_timestep,end_step,Timestep,Ideal Gas,Viscosity,PdV,Revert,Acceleration,Fluxes,Cell Advection,Momentum Advection,Reset,Summary,Visit,Tile Halo Exchange,Self Halo Exchange,MPI Halo Exchange,Total,The Rest -random_test,0fdb917bf10d20363dd8b88d762851908643925b,caxwl,2021-08-09 12:23:14,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0546,0.0374,0.0357,0.1419,0.0222,0.0601,0.0470,0.1369,0.3139,0.0339,0.0022,0.0000,0.0001,0.2134,0.5226,1.6218,0.0003 -random_test,1398cd9cfa7570db9c8eb7a17a2f3e698c90aeb4,GitHub,2021-08-03 08:49:14,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0497,0.0354,0.0365,0.1285,0.0239,0.0639,0.0460,0.1288,0.3257,0.0311,0.0016,0.0000,0.0002,0.2176,0.4954,1.5844,0.0003 -random_test,158e23d08f73d36f71e144851451955b3ae02dff,mohan002,2021-08-02 18:40:45,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0567,0.0353,0.0365,0.1425,0.0223,0.0589,0.0448,0.1369,0.3030,0.0323,0.0021,0.0000,0.0002,0.2011,0.5384,1.6110,0.0003 -random_test,2efdde26ab1eef67dfce5a29163e8d513b6dffc4,jdshanks,2020-08-20 14:20:55,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0625,0.0422,0.0347,0.1608,0.0161,0.0651,0.0448,0.1439,0.2851,0.0389,0.0069,0.0000,0.0003,0.1667,0.5370,1.6048,0.0003 -random_test,89cc919b28f687a25d30b44ddf547201da930c14,jdshanks,2020-07-14 09:16:46,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0503,0.0357,0.0343,0.1332,0.0231,0.0582,0.0443,0.1242,0.3041,0.0302,0.0019,0.0000,0.0001,0.2165,0.5463,1.6025,0.0003 -random_test,07fcf4d773ba7626e6ea36c7002f7b2cd7c76b2a,jdshanks,2020-07-14 08:44:25,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0592,0.0420,0.0339,0.1427,0.0277,0.0637,0.0453,0.1417,0.2957,0.0383,0.0049,0.0000,0.0002,0.1820,0.5184,1.5957,0.0003 -random_test,e37e1d7aab99070a65094e784721b4d05fb86444,jdshanks,2020-07-14 08:13:22,UK-MAC/CloverLeaf_ref,1.300,4,4,960,960,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.0630,0.0371,0.0375,0.1360,0.0211,0.0604,0.0441,0.1337,0.3068,0.0330,0.0020,0.0000,0.0001,0.1939,0.5274,1.5960,0.0003 -random_test,439c8d846ede012c89f7be451763a32dbaa5eb2c,GitHub,2020-07-13 20:16:34,UK-MAC/CloverLeaf_ref,1.300,4,4,3840,3840,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.7595,0.5283,0.6273,2.1090,0.3632,0.9054,0.6695,2.3710,4.9192,0.5662,0.0367,0.0000,0.0003,0.8092,0.9885,15.6531,0.0005 -random_test,3f889495db94c6fba5a5ec1f9937f49e8b66f94d,Olly Perks,2020-07-13 14:02:03,UK-MAC/CloverLeaf_ref,1.300,4,4,3840,3840,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.8014,0.5378,0.6328,2.0701,0.3678,0.9438,0.6536,2.3394,4.9349,0.5455,0.0372,0.0000,0.0003,0.7951,1.0069,15.6668,0.0009 -random_test,b5e598dc0f10ca804dce4a748e3c2314545269cd,GitHub,2020-07-03 08:48:29,UK-MAC/CloverLeaf_ref,1.300,4,4,3840,3840,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.7889,0.5249,0.6021,2.1048,0.3488,0.9416,0.6636,2.3405,4.9103,0.5517,0.0298,0.0000,0.0003,0.8212,1.0441,15.6725,0.0004 -random_test,821792e5b424d84b6043c5dfa22ae3cc4e67fa0f,jdshanks,2020-07-03 08:37:23,UK-MAC/CloverLeaf_ref,1.300,4,4,3840,3840,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.8475,0.5778,0.6166,2.1963,0.3285,0.8830,0.6693,2.3973,4.7511,0.5906,0.0654,0.0000,0.0003,0.2384,1.4976,15.6597,0.0015 -random_test,34869cbce7ba601168e65ea1bbb718e3688d08fa,jdshanks,2020-07-01 17:58:18,UK-MAC/CloverLeaf_ref,1.300,4,4,3840,3840,0.0,0.0,10.0,10.0,0.04,1.5,0.04,87,0.7903,0.5164,0.6468,2.0930,0.3509,0.9221,0.6599,2.3471,4.9155,0.5468,0.0417,0.0000,0.0003,0.8198,1.0058,15.6562,0.0038 \ No newline at end of file diff --git a/examples/perf_analyzer/db_backups/clover_random_test.db b/examples/perf_analyzer/db_backups/clover_random_test.db deleted file mode 100644 index 762b09fa8f26bd3170d6d3ab754a1508a4876c2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeI0OK&7s8HU@=gvo7Y2*HdZG)W^EDM)tZ+%FOmHpU2vNhAgbWrJGhR_@O9#p*5( z_HxXEzrdehkN6X;_z$dDvgZA&Y{#}H9y=Z(7F}{zSDiY2>U>YV-}{~N?|%Da)RcZb zn@>X1m%Z=yR#tkO{eG|4dmo=wd`>qyzH@ql@0D}M-*otXubTe-9(BI|y&nFS+mgVN zz>>g{z>>g{z>>g{z>>g{z>>g{!2b<_KRsKy`|#tBSN{CC32|I@w|?^+q-{x=kV<>-*R^!zWJX!|9s=GtN&g#EBA5v^7dl) z!Q&tQVfG{Y@WF!*u&u4FeXvIVFu1s_&BHX$CIeE2v|dP|3{NFV6{Lu@GEQ5rx!^n{ zAtIB)g|G4g5kFgO)(7Ju)U+|4?Tx0q8bwx}G*x9krK;pAr0U?AV1jKhyTSMuzuEX~ zYx~pf@eI%9NeML&)Vz|6Ddi2rAFJq(pM3D8c%|vfS;BGr_&&n=c^eiYItlB;i`x%B zybzXh#;2HyjlnR^jVnf%z+;S-yCRKqnYlXT*}>j8EUK@nlkj3_hKS+ddefIqG5TPx zCzGcjXy#`Du*x&-S*4wptTIw^Q3)>4NlNK(6%f#_l@paTN~y{UVbR}c8cSY}~NiPfB?fV<~ZRImyeoP^{`D=m=c`P<6#T7Vo^2vrM>u#tCKR9sUoOQjJu z(viSvp#Vq-m0pG zrj9`eT@tUom$p{ExM)o6Ri2Ht2FO7(985eZ$T3DTV+=zOpto)cNJ*by3(AyS7#GR; z+QyTR7CtTbEjZ~HZNj!fjNP7VCmQoDO=#R`p z?2une;0zEr3zQedsRCHldaN_X2*}db3;DN)~Qw*i5i22xQi)4nq+ZD2uY-DlQmJidLi)| z_-G;^$)~X=4yrX5(JmB_c4${*MjSpwd~C~y785Vw!wlshRuqtuCnEzp@Okrwe4J0o zb03APjk7`MELntHa;+gD#&dwY6aSy+r!(A6Ka>^V(CUX+!FVXf*0Oo)KT;}cmoO~i zL#jKkAK}1Ib8KY#-Boe?=*|uKh*IZVgb9(CQX8A=RCExY8(Cb|Tt<`Y8mpPB7ou)M zuv_O>n;mUtbyj&+UcS>t1F%Gp{;3M%|!3WDw&{FU+sRg7H)KsSXzP5!F4p5^dnfuv!*_i=KrN)l19PYXd_ewi!cjVw6uX3i4wL z8gC(VwF5(yAIU~7?Mux|{H9$U;HA<54Bo!b6P=FNRsiPH8|VdV;^?`JSP*7GHkh-E zc$tGYLo}YDSV=E*OyTg4g!=0WPgz+MMdRwB8~G bREVBk23_zO^cP3~$|FTDb%NI2;3EGE#OP&A diff --git a/examples/perf_analyzer/environment.yml b/examples/perf_analyzer/environment.yml deleted file mode 100644 index 5ce047da..00000000 --- a/examples/perf_analyzer/environment.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: cdsi -channels: - - conda-forge - - defaults -dependencies: - - ca-certificates=2024.6.2=hf0a4a13_0 - - cctools=986=h4c9edd9_0 - - cctools_osx-arm64=986=hd11630f_0 - - clang=18.1.8=default_h675cc0c_0 - - clang-18=18.1.8=default_h5c12605_0 - - clang_impl_osx-arm64=18.1.8=h2ae9ea5_16 - - clang_osx-arm64=18.1.8=h54d7cd3_16 - - clangxx=18.1.8=default_h675cc0c_0 - - compiler-rt=18.1.8=h28df8ea_0 - - compiler-rt_osx-arm64=18.1.8=h56c4e69_0 - - gfortran=13.2.0=h1ca8e4b_1 - - gfortran_impl_osx-arm64=13.2.0=h30f4408_1 - - gfortran_osx-arm64=13.2.0=h57527a5_1 - - gmp=6.3.0=h7bae524_2 - - icu=73.2=hc8870d7_0 - - isl=0.25=h9a09cb3_0 - - ld64=711=h4c6efb1_0 - - ld64_osx-arm64=711=h5e7191b_0 - - libclang-cpp18.1=18.1.8=default_h5c12605_0 - - libcxx=17.0.6=he7857fb_1 - - libevent=2.1.12=h2757513_1 - - libgfortran=5.0.0=13_2_0_hd922786_3 - - libgfortran-devel_osx-arm64=13.2.0=h5d7a38c_3 - - libgfortran5=13.2.0=hf226fd6_3 - - libhwloc=2.10.0=default_h7685b71_1001 - - libiconv=1.17=h0d3ecfb_2 - - libllvm18=18.1.8=h5090b49_0 - - libxml2=2.12.7=ha661575_1 - - libzlib=1.3.1=hfb2fe0b_1 - - llvm-openmp=18.1.8=hde57baf_0 - - llvm-tools=18.1.8=h5090b49_0 - - mpc=1.3.1=h91ba8db_0 - - mpfr=4.2.1=h41d338b_1 - - mpi=1.0=openmpi - - openmpi=5.0.3=he01d045_107 - - openssl=3.3.1=hfb2fe0b_1 - - sigtool=0.1.3=h44b9a77_0 - - tapi=1100.0.11=he4954df_0 - - xz=5.2.6=h57fd34a_0 - - zlib=1.3.1=hfb2fe0b_1 - - zstd=1.5.6=hb46c0d2_0 -prefix: /Users/ssakin/Softwares/anaconda3/envs/cdsi diff --git a/examples/perf_analyzer/fly_server.py b/examples/perf_analyzer/fly_server.py deleted file mode 100755 index 717b0452..00000000 --- a/examples/perf_analyzer/fly_server.py +++ /dev/null @@ -1,1041 +0,0 @@ -# #!/usr/bin/env python3 - -import argparse -from dash import Dash, html, dcc, Input, Output, dash_table, callback, State, callback_context, no_update -import dash_bootstrap_components as dbc - -import plotly.graph_objects as go -from plotly.subplots import make_subplots -import numpy as np -import pandas as pd -import re -# import git -from github import Github -import diskcache -import os -import git -import subprocess - -import parse_output_analyzer -import code_sensing - -fetched_branches = list() -repo_name = "" -local_cached_data = None - -class PerfRunner(): - def __init__(self, cwd='/tmp/fly_dsi'): - self.testname = "perf_runner_test_run" - self.current_working_directory = cwd - self.current_git_directory = cwd + '/src' - - def initGitRepo(self, gup='UK-MAC/CloverLeaf_ref'): - self.git_user_repo = gup - self.git_http_url = 'https://github.com/' + self.git_user_repo + '.git' - self.git_ssh_url = 'git@github.com:' + self.git_user_repo + '.git' - self.updateGitRepo() - - def updateGitRepo(self): - self.git_repo = None - if not os.path.exists(self.current_git_directory): - os.mkdir(self.current_git_directory) - try: - self.git_repo = git.Repo(self.current_git_directory) - except git.InvalidGitRepositoryError: - self.git_repo = git.Repo.clone_from(self.git_ssh_url, self.current_git_directory) - - - -def initAndLoadCachedData(workignDir='/tmp/fly_dsi'): - global local_cached_data - if not os.path.exists(workignDir): - print("Cache path not exist. Creating directory.") - os.makedirs(workignDir) - print("Loading cached data ...") - cpath = os.path.join(workignDir, 'dsi_perf.diskCacheIndex') - local_cached_data = diskcache.Index(cpath) - if 'git_nodes' not in local_cached_data: - local_cached_data['git_nodes'] = dict() - print("Cached data loaded") - - - -def getGitRepo(user_repo): - GITHUB_ACCESS_TOKEN = "" - g = Github(GITHUB_ACCESS_TOKEN,verify=True) - return g.get_repo(user_repo) - -def getGitGraph(user_repo, selected_branches): - # considering perf_data is already sorted by commit time - global fetched_branches - global local_cached_data - cached_data = local_cached_data['git_nodes'] - - all_nodes_list = list() - repo = None - for current_selected_branches in selected_branches: - if current_selected_branches in cached_data: - all_nodes_list.extend(cached_data[current_selected_branches]) - else: - - print("Trying to get git graphs") - git_graph_max_depth = 8 - - visited_commit_hash = [] - commit_queue = [] - nodes_list = [] - repo = getGitRepo(user_repo) - # checked_branches_list = ["master", "feature/CMake", "feature/caliper", "fix_gcc", "parse_fix", "development"] - for each_branch in fetched_branches: - if each_branch.name == current_selected_branches: - commit_queue.append({"commit":each_branch.commit,"branch":each_branch.name, "depth":0}) - # print(each_branch.commit.commit.committer.date.strftime("%b-%d,%Y(%H:%M)")) - - while len(commit_queue) > 0: - top_here = commit_queue.pop() - c_commit = top_here["commit"] - if c_commit.commit.sha in visited_commit_hash: - continue - if top_here["depth"] > git_graph_max_depth: - continue - visited_commit_hash.append(c_commit.commit.sha) - - for each_parent in c_commit.commit.parents: - nodes_list.append({"sha":c_commit.commit.sha, - "date":c_commit.commit.committer.date, - "depth":top_here["depth"], - "branch":top_here["branch"], - "message":c_commit.commit.message, - "cname":c_commit.commit.committer.name - }) - child_commit = repo.get_commit(sha=each_parent.sha) - nodes_list.append({"sha":child_commit.commit.sha, - "date":child_commit.commit.committer.date, - "depth":top_here["depth"]+1, - "branch":top_here["branch"], - "message":None, - "cname":None - }) - if child_commit.commit.sha not in visited_commit_hash: - commit_queue.append({"commit":child_commit,"branch":top_here["branch"], "depth":top_here["depth"]+1}) - # nodes_list = [{'sha': '07d783c332e3ac0fb63a743736d2707838b429c9', 'date': datetime.datetime(2022, 12, 24, 14, 26, 8, tzinfo=datetime.timezone.utc), 'depth': 0, 'branch': 'feature/CMake', 'message': 'merge master changes + cmake update', 'cname': 'caxwl'}, {'sha': '0e66b53c9c876a7f8298ef31666c9b52dbbbfd75', 'date': datetime.datetime(2022, 12, 24, 14, 14, 59, tzinfo=datetime.timezone.utc), 'depth': 1, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '0e66b53c9c876a7f8298ef31666c9b52dbbbfd75', 'date': datetime.datetime(2022, 12, 24, 14, 14, 59, tzinfo=datetime.timezone.utc), 'depth': 1, 'branch': 'feature/CMake', 'message': 'file correction', 'cname': 'caxwl'}, {'sha': 'de4876d5a45af5c85100af17188c2fc7d3c831bf', 'date': datetime.datetime(2022, 12, 24, 14, 2, 54, tzinfo=datetime.timezone.utc), 'depth': 2, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '0e66b53c9c876a7f8298ef31666c9b52dbbbfd75', 'date': datetime.datetime(2022, 12, 24, 14, 14, 59, tzinfo=datetime.timezone.utc), 'depth': 1, 'branch': 'feature/CMake', 'message': 'file correction', 'cname': 'caxwl'}, {'sha': '0fdb917bf10d20363dd8b88d762851908643925b', 'date': datetime.datetime(2021, 8, 9, 12, 23, 14, tzinfo=datetime.timezone.utc), 'depth': 2, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '0fdb917bf10d20363dd8b88d762851908643925b', 'date': datetime.datetime(2021, 8, 9, 12, 23, 14, tzinfo=datetime.timezone.utc), 'depth': 2, 'branch': 'feature/CMake', 'message': 'OMP flag update', 'cname': 'caxwl'}, {'sha': '1398cd9cfa7570db9c8eb7a17a2f3e698c90aeb4', 'date': datetime.datetime(2021, 8, 3, 8, 49, 14, tzinfo=datetime.timezone.utc), 'depth': 3, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '1398cd9cfa7570db9c8eb7a17a2f3e698c90aeb4', 'date': datetime.datetime(2021, 8, 3, 8, 49, 14, tzinfo=datetime.timezone.utc), 'depth': 3, 'branch': 'feature/CMake', 'message': 'Merge pull request #7 from amd-toolchain-support/aocc_support', 'cname': 'GitHub'}, {'sha': '2efdde26ab1eef67dfce5a29163e8d513b6dffc4', 'date': datetime.datetime(2020, 8, 20, 14, 20, 55, tzinfo=datetime.timezone.utc), 'depth': 4, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '1398cd9cfa7570db9c8eb7a17a2f3e698c90aeb4', 'date': datetime.datetime(2021, 8, 3, 8, 49, 14, tzinfo=datetime.timezone.utc), 'depth': 3, 'branch': 'feature/CMake', 'message': 'Merge pull request #7 from amd-toolchain-support/aocc_support', 'cname': 'GitHub'}, {'sha': '158e23d08f73d36f71e144851451955b3ae02dff', 'date': datetime.datetime(2021, 8, 2, 18, 40, 45, tzinfo=datetime.timezone.utc), 'depth': 4, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '158e23d08f73d36f71e144851451955b3ae02dff', 'date': datetime.datetime(2021, 8, 2, 18, 40, 45, tzinfo=datetime.timezone.utc), 'depth': 4, 'branch': 'feature/CMake', 'message': 'adding AOCC compiler support for CloverLeaf_ref', 'cname': 'mohan002'}, {'sha': '2efdde26ab1eef67dfce5a29163e8d513b6dffc4', 'date': datetime.datetime(2020, 8, 20, 14, 20, 55, tzinfo=datetime.timezone.utc), 'depth': 5, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '2efdde26ab1eef67dfce5a29163e8d513b6dffc4', 'date': datetime.datetime(2020, 8, 20, 14, 20, 55, tzinfo=datetime.timezone.utc), 'depth': 5, 'branch': 'feature/CMake', 'message': 'Fix typos in C kernels.', 'cname': 'jdshanks'}, {'sha': '89cc919b28f687a25d30b44ddf547201da930c14', 'date': datetime.datetime(2020, 7, 14, 9, 16, 46, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '89cc919b28f687a25d30b44ddf547201da930c14', 'date': datetime.datetime(2020, 7, 14, 9, 16, 46, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': 'Add ivdep in place of previous simd.', 'cname': 'jdshanks'}, {'sha': '07fcf4d773ba7626e6ea36c7002f7b2cd7c76b2a', 'date': datetime.datetime(2020, 7, 14, 8, 44, 25, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '07fcf4d773ba7626e6ea36c7002f7b2cd7c76b2a', 'date': datetime.datetime(2020, 7, 14, 8, 44, 25, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': 'Unaligned.', 'cname': 'jdshanks'}, {'sha': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'date': datetime.datetime(2020, 7, 14, 8, 13, 22, tzinfo=datetime.timezone.utc), 'depth': 8, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'date': datetime.datetime(2020, 7, 14, 8, 13, 22, tzinfo=datetime.timezone.utc), 'depth': 8, 'branch': 'feature/CMake', 'message': 'Remove simd directives, causing loops to not vectorise under LLVM/clang.', 'cname': 'jdshanks'}, {'sha': '439c8d846ede012c89f7be451763a32dbaa5eb2c', 'date': datetime.datetime(2020, 7, 13, 20, 16, 34, tzinfo=datetime.timezone.utc), 'depth': 9, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': 'de4876d5a45af5c85100af17188c2fc7d3c831bf', 'date': datetime.datetime(2022, 12, 24, 14, 2, 54, tzinfo=datetime.timezone.utc), 'depth': 2, 'branch': 'feature/CMake', 'message': 'cleanup before merge', 'cname': 'caxwl'}, {'sha': '37c34c42421f605e73f3698bbbbfe7181e82615e', 'date': datetime.datetime(2020, 5, 6, 14, 48, 4, tzinfo=datetime.timezone.utc), 'depth': 3, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '37c34c42421f605e73f3698bbbbfe7181e82615e', 'date': datetime.datetime(2020, 5, 6, 14, 48, 4, tzinfo=datetime.timezone.utc), 'depth': 3, 'branch': 'feature/CMake', 'message': 'Cleanup build', 'cname': 'Wei'}, {'sha': '9e4093c80cbdb8ac33497569ad0d217c1e8d386c', 'date': datetime.datetime(2020, 5, 6, 14, 44, 38, tzinfo=datetime.timezone.utc), 'depth': 4, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '9e4093c80cbdb8ac33497569ad0d217c1e8d386c', 'date': datetime.datetime(2020, 5, 6, 14, 44, 38, tzinfo=datetime.timezone.utc), 'depth': 4, 'branch': 'feature/CMake', 'message': 'Initial CMake changes', 'cname': 'Wei'}, {'sha': '6cf9b1a810531cf691be3b080c7ecfd73019c9b3', 'date': datetime.datetime(2020, 5, 6, 14, 29, 46, tzinfo=datetime.timezone.utc), 'depth': 5, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '6cf9b1a810531cf691be3b080c7ecfd73019c9b3', 'date': datetime.datetime(2020, 5, 6, 14, 29, 46, tzinfo=datetime.timezone.utc), 'depth': 5, 'branch': 'feature/CMake', 'message': "Merge remote-tracking branch 'origin/parse_fix' into feature/CMake", 'cname': 'Wei'}, {'sha': 'c97a402a197d1b7df1b230cf39a59ef505570c50', 'date': datetime.datetime(2016, 5, 23, 10, 28, 22, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '6cf9b1a810531cf691be3b080c7ecfd73019c9b3', 'date': datetime.datetime(2020, 5, 6, 14, 29, 46, tzinfo=datetime.timezone.utc), 'depth': 5, 'branch': 'feature/CMake', 'message': "Merge remote-tracking branch 'origin/parse_fix' into feature/CMake", 'cname': 'Wei'}, {'sha': '8d0cfdebc707e9372384a68635bf607772bc4ba5', 'date': datetime.datetime(2019, 3, 11, 13, 7, 50, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '8d0cfdebc707e9372384a68635bf607772bc4ba5', 'date': datetime.datetime(2019, 3, 11, 13, 7, 50, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': 'Fix error in argument parsing', 'cname': 'Tom Deakin'}, {'sha': '0ddf495cf21cc59f84e274617522a1383e2c328c', 'date': datetime.datetime(2015, 10, 28, 8, 39, 46, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '0ddf495cf21cc59f84e274617522a1383e2c328c', 'date': datetime.datetime(2015, 10, 28, 8, 39, 46, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': 'Update to version 1.3 For Mantevo SC2015', 'cname': 'Oliver Perks'}, {'sha': '4f23dd3beb6376f0b6d4922336943760664d5b9a', 'date': datetime.datetime(2015, 8, 5, 22, 32, 14, tzinfo=datetime.timezone.utc), 'depth': 8, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '4f23dd3beb6376f0b6d4922336943760664d5b9a', 'date': datetime.datetime(2015, 8, 5, 22, 32, 14, tzinfo=datetime.timezone.utc), 'depth': 8, 'branch': 'feature/CMake', 'message': 'Added pack/unpack message methods to qextname list for IBM compilers', 'cname': 'David Beckingsale'}, {'sha': '3822dab44664b151a857da568e4b0974ac807195', 'date': datetime.datetime(2015, 8, 5, 13, 45, 53, tzinfo=datetime.timezone.utc), 'depth': 9, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': 'c97a402a197d1b7df1b230cf39a59ef505570c50', 'date': datetime.datetime(2016, 5, 23, 10, 28, 22, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': 'Merge pull request #3 from AndrewMallinson/master_v1.3_UHnosync\n\nMaster v1.3 u hnosync', 'cname': 'OliverPerks'}, {'sha': '0ddf495cf21cc59f84e274617522a1383e2c328c', 'date': datetime.datetime(2015, 10, 28, 8, 39, 46, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': 'c97a402a197d1b7df1b230cf39a59ef505570c50', 'date': datetime.datetime(2016, 5, 23, 10, 28, 22, tzinfo=datetime.timezone.utc), 'depth': 6, 'branch': 'feature/CMake', 'message': 'Merge pull request #3 from AndrewMallinson/master_v1.3_UHnosync\n\nMaster v1.3 u hnosync', 'cname': 'OliverPerks'}, {'sha': '57875df8f3dcf4ffd79df41d621e6577dc6c2d16', 'date': datetime.datetime(2016, 5, 5, 14, 48, 27, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': '57875df8f3dcf4ffd79df41d621e6577dc6c2d16', 'date': datetime.datetime(2016, 5, 5, 14, 48, 27, tzinfo=datetime.timezone.utc), 'depth': 7, 'branch': 'feature/CMake', 'message': 'updated CHANGE LOG to capture the optimisation to update halo.f90', 'cname': 'Andrew Mallinson'}, {'sha': 'a144711c6e803e71b9e8df7b515deb62ccfab2a5', 'date': datetime.datetime(2016, 2, 19, 14, 50, 2, tzinfo=datetime.timezone.utc), 'depth': 8, 'branch': 'feature/CMake', 'message': None, 'cname': None}, {'sha': 'a144711c6e803e71b9e8df7b515deb62ccfab2a5', 'date': datetime.datetime(2016, 2, 19, 14, 50, 2, tzinfo=datetime.timezone.utc), 'depth': 8, 'branch': 'feature/CMake', 'message': 'modified update halo kernel.f90 to reduce sync to one barrier\n\nstill need to test', 'cname': 'Andrew Mallinson'}, {'sha': 'f274ea05a6c8adde7fafcd6ba614dd845a2852df', 'date': datetime.datetime(2016, 2, 11, 17, 42, 46, tzinfo=datetime.timezone.utc), 'depth': 9, 'branch': 'feature/CMake', 'message': None, 'cname': None}] - cached_data[current_selected_branches] = nodes_list - all_nodes_list.extend(nodes_list) - # print(nodes_list) - local_cached_data['git_nodes'] = cached_data - print('fetched data from ', selected_branches) - return all_nodes_list - - -# Fenwick tree range sum functions -# ================================= -def sum(idx, F): - running_sum = 0 - while idx > 0: - running_sum += F[idx] - right_most_set_bit = (idx & -idx) - idx -= right_most_set_bit - return running_sum - -def add(idx, X, F): - while idx < len(F): - F[idx] += X - right_most_set_bit = (idx & -idx) - idx += right_most_set_bit - -def point_query(idx, F): - return sum(idx, F) - -def range_update(idx, r, X, F): - # Add X to the element at index l - add(idx, X, F) - # Subtract X from the element at index (r + 1) - add(r + 1, -X, F) -# ================================= - -def generateGitChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()): - if git_nodes is None or len(git_nodes) == 0: - fig1 = go.Figure().add_annotation( - x=2, y=2, - text="Select Branches", - font=dict(family="sans serif",size=25,color="crimson"), - showarrow=False, - yshift=10) - fig1.update_layout( - xaxis = { "visible": False }, - yaxis = { "visible": False }) - return fig1 - # gitFig = go.Figure() - gitFig = make_subplots(rows=3, cols=1, - vertical_spacing = 0.01, - shared_xaxes=True, - row_heights=[0.6, 0.1, 0.3], - subplot_titles=("hello","")) - - branch_depth = dict() - hash_depth = dict() - - git_nodes_df = pd.DataFrame(git_nodes) - - merged_df = git_nodes_df - if sorted_df is not None: - merged_df = pd.merge(sorted_df, git_nodes_df, left_on="git_hash", right_on="sha", how="outer") - combined_all_df = merged_df[merged_df.cname != None].sort_values(by=['date'], ascending=True) - combined_all_df["formatted_date"] = pd.to_datetime(combined_all_df['date']).dt.strftime("%b-%d,%Y(%H:%M:%S)") - sorted_git_nodes_df = git_nodes_df.sort_values(by=['date'], ascending=True) - - Xe = [] - Ye = [] - unique_git_nodes_dates_df = sorted_git_nodes_df.drop_duplicates().reset_index(drop=True) - - def getNodeWidth(sha, depth): - if sha not in hash_depth: - branch_depth[depth] = branch_depth.get(depth, 0) + 1 - hash_depth[sha] = branch_depth[depth] - return hash_depth[sha] - - branch_head_Y = dict() - def getNodeY(go): - if go['sha'] not in hash_depth: - le = unique_git_nodes_dates_df['date'].loc[lambda x: x == go['date']] - cp = point_query(le.index[0], F) - - if go['depth'] == 0: - branch_head_Y[go['branch']] = cp - else: - if go['branch'] in branch_head_Y: - cp = cp + branch_head_Y[go['branch']] - hash_depth[go['sha']] = cp - return hash_depth[go['sha']] - - - F = [0] * (len(unique_git_nodes_dates_df.index) + 1) - for ind in git_nodes_df.index: - if ind % 2 == 1: - le = unique_git_nodes_dates_df['date'].loc[lambda x: x == git_nodes_df['date'][ind-1]] - re = unique_git_nodes_dates_df['date'].loc[lambda x: x == git_nodes_df['date'][ind]] - # print(git_nodes_df['date'][ind-1], le.index[0], ' == ', end='') - # print(git_nodes_df['date'][ind], re.index[0]) - range_update(re.index[0]+1, le.index[0]-1, 1, F) - - def find_commit_dates(hash): - return combined_all_df[combined_all_df["sha"] == hash]["formatted_date"].iloc[0] - - def make_annotations(font_size=15, font_color='rgb(0,0,0)'): - annotations = [] - for ind in git_nodes_df.index: - if git_nodes_df['depth'][ind] == 0: - annotations.append( - dict( - text=git_nodes_df['branch'][ind], - x=find_commit_dates(git_nodes_df['sha'][ind]), - y=getNodeY(git_nodes_df.loc[ind]), - xref='x3', yref='y3', - font=dict(color=font_color, size=font_size),# textangle=-90, - showarrow=True, arrowhead=2, arrowsize=1, arrowwidth=2, - align="right", valign="top", xanchor="auto", yanchor="bottom" - ) - ) - return annotations - cd = [] - edges = list() - XY_ind = 0 - for ind in git_nodes_df.index: - if ind % 2 == 1: - # le = getNodeWidth(git_nodes_df['sha'][ind-1], git_nodes_df['depth'][ind-1]) - # re = getNodeWidth(git_nodes_df['sha'][ind], git_nodes_df['depth'][ind]) - - le = getNodeY(git_nodes_df.loc[ind-1]) - re = getNodeY(git_nodes_df.loc[ind]) - - Xe += [find_commit_dates(git_nodes_df['sha'][ind-1]), find_commit_dates(git_nodes_df['sha'][ind]), None] - Ye += [le, re, None] - edges.append(((git_nodes_df['date'][ind-1],XY_ind),(git_nodes_df['date'][ind],XY_ind))) - cd += [[git_nodes_df['message'][ind-1], git_nodes_df['sha'][ind-1], git_nodes_df['cname'][ind-1]], - [git_nodes_df['message'][ind], git_nodes_df['sha'][ind], git_nodes_df['cname'][ind]], - None] - XY_ind = XY_ind + 1 - - gitFig.add_trace(go.Scatter(x=Xe, - y=Ye, - line_shape='hv', - mode='lines+markers', - marker=dict(symbol='circle-dot', - size=8, - color='#6175c1', - line=dict(color='rgb(50,50,50)', width=1) - ), - name='', - hovertemplate = - 'Message: %{customdata[0]}
Name: %{customdata[2]}', - showlegend = False, - customdata=cd), - row = 3, - col = 1, - secondary_y = False - ) - - if sorted_df is not None and len(sorted_df) > 0: - for col_name in perf_filter:# ["pdv", "cell_advection", "mpi_halo_exchange", "self_halo_exchange", "momentum_advection", "total"]: - gitFig.add_trace(go.Scatter(x=combined_all_df["formatted_date"], y=combined_all_df[col_name], - mode='lines', - name=col_name), - row = 1, - col = 1, - secondary_y = False - ) - - if mk_data is not None: - print('mk data found', mk_data['hash']) - - global perf_runner - global local_cached_data - cached_data = local_cached_data['code_sensing'] - all_var_list = list() - for each_hash in cached_data: - - if mk_data['ev'] in cached_data[each_hash] and \ - mk_data['var_name'] in cached_data[each_hash][mk_data['ev']] and \ - mk_data['file_name'] in cached_data[each_hash][mk_data['ev']][mk_data['var_name']]: - f_list = dict() - f_list['hash'] = each_hash - f_list['occ'] = len(cached_data[each_hash][mk_data['ev']][mk_data['var_name']][mk_data['file_name']]) - all_var_list.append(f_list) - - all_var_df = pd.DataFrame(all_var_list) - - code_senese_df = pd.merge(all_var_df, git_nodes_df, left_on="hash", right_on="sha", how="outer") - code_senese_df = code_senese_df.dropna().drop_duplicates(subset=['hash'], keep='first').reset_index(drop=True) - code_senese_df["formatted_date"] = pd.to_datetime(code_senese_df['date']).dt.strftime("%b-%d,%Y(%H:%M:%S)") - print(code_senese_df.dropna()['occ']) - print(code_senese_df.dropna()['formatted_date']) - gitFig.add_trace(go.Bar(x=code_senese_df.dropna()["formatted_date"], y=code_senese_df.dropna()['occ'], - marker = {'color' : 'black'}, - name=mk_data['file_name']), - row = 2, - col = 1, - secondary_y = False - ) - - - gitFig.update_traces(mode='lines+markers', row=1, col=1) - gitFig.update_xaxes(showgrid=False, categoryorder='array', categoryarray=combined_all_df["formatted_date"]) - gitFig.update_xaxes(visible=False, showticklabels=False, row=1, col=1) - gitFig.update_yaxes(visible=True, showgrid=False, showticklabels=False, row=2, col=1) - gitFig.update_yaxes(visible=True, showticklabels=False, title="Commits", row=3, col=1) - gitFig.update_yaxes(type="linear", title="Time (s)", row=1, col=1) - gitFig.update_layout(margin=dict(l=20, r=20, t=0, b=20), - annotations=make_annotations(), - legend_traceorder="normal", - dragmode="select", - hovermode="x unified") - gitFig.update_traces(xaxis='x3') - return gitFig - -def generateParCordChart(sorted_df, git_nodes, mk_data=None, perf_filter=list()): - if git_nodes is None or len(git_nodes) == 0: - fig1 = go.Figure().add_annotation( - x=2, y=2, - text="Select Branches", - font=dict(family="sans serif",size=25,color="crimson"), - showarrow=False, - yshift=10) - fig1.update_layout( - xaxis = { "visible": False }, - yaxis = { "visible": False }) - return fig1 - - git_nodes_df = pd.DataFrame(git_nodes) - - merged_df = git_nodes_df - if sorted_df is not None: - merged_df = pd.merge(sorted_df, git_nodes_df, left_on="git_hash", right_on="sha", how="outer") - combined_all_df = merged_df[merged_df.cname != None].sort_values(by=['date'], ascending=True) - combined_all_df["formatted_date"] = pd.to_datetime(combined_all_df['date']).dt.strftime("%b-%d,%Y(%H:%M:%S)") - - combined_all_df = combined_all_df\ - .dropna(subset=perf_filter.extend(["date"])).reset_index(drop=True)\ - .drop_duplicates(subset=['formatted_date'], keep='first') - - # dims = [] - # labels = [] - - print(combined_all_df["formatted_date"]) - # print(combined_all_df["branch"]) - parCordValues = list() - if combined_all_df is not None and len(combined_all_df) > 0: - f_col = combined_all_df.index.values - # labels.append("Commit
Time") - # dims.append(f_col) - parCordValues.append( - dict(range = [f_col.min(), f_col.max()], - values = f_col, - label = "
Commit
Time", tickvals = f_col, ticktext = combined_all_df["formatted_date"].astype(str)) - ) - for col_name in perf_filter:# ["pdv", "cell_advection", "mpi_halo_exchange", "self_halo_exchange", "momentum_advection", "total"]: - if col_name == "date": - continue - f_col = combined_all_df[col_name].astype(float) - # Replace the second to last underscore with a white space in the label - parts = col_name.split('_') - csum = 0 - label = '
' - for part in parts: - if csum > 0: - label += '_' - csum += len(part) + 1 - label += part - if csum > len(col_name) // 2: - label += "
" - csum = 0 - # labels.append(label) - # dims.append(f_col) - parCordValues.append( - dict(range = [f_col.min(), f_col.max()], - label = label, values = f_col) - ) - # if len(dims) == 0: - if len(parCordValues) == 0: - fig = go.Figure().add_annotation( - x=2, y=2, - text="No data to plot", - font=dict(family="sans serif",size=25,color="crimson"), - showarrow=False, - yshift=10) - fig.update_layout( - xaxis = { "visible": False }, - yaxis = { "visible": False }) - # return fig - else: - fig = go.Figure(data=go.Parcoords( - line = dict(color = parCordValues[0]['values'], - colorscale = [[0, 'blue'], [1, 'blue']], - showscale = False, - colorbar = dict(x=-0.15, showticklabels=False), - cmin = parCordValues[0]['range'][0], - cmax = parCordValues[0]['range'][1]), - dimensions = parCordValues, - labelangle = 0, - labelside = "bottom", - labelfont = dict(size=16, color='black'), - rangefont = dict(size=14, color='black'), - tickfont = dict(size=12, color='black'), - ) - ) - fig.update_layout(margin=dict(l=160)) - return fig - -# @callback( -# Output('perf_graph', 'figure'), -# Input('git_graph', 'hoverData')) -# def update_perf_graph(hoverData): -# current_hash = hoverData['points'][0]['customdata'] -# current_time = hoverData['points'][0]['x'] -# return generatePerfChart(current_time) - -def main(perf_data, git_nodes): - js_scripts = [ - { 'src':"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js", - 'integrity':"sha512-6yoqbrcLAHDWAdQmiRlHG4+m0g/CT/V9AGyxabG8j7Jk8j3r3K6due7oqpiRMZqcYe9WM2gPcaNNxnl2ux+3tA==", - 'crossorigin':"anonymous", - 'referrerpolicy':"no-referrer" - } - ] - css_scripts = [ - { - 'rel':"stylesheet", - 'href':"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github-dark-dimmed.min.css", - 'integrity':"sha512-zcatBMvxa7rT7dDklfjauWsfiSFParF+hRfCdf4Zr40/MmA1gkFcBRbop0zMpvYF3FmznYFgcL8wlcuO/GwHoA==", - 'crossorigin':"anonymous", - 'referrerpolicy':"no-referrer" - }, - # { - # 'rel':"stylesheet", - # 'href':"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/github.min.css", - # 'integrity':"sha512-0aPQyyeZrWj9sCA46UlmWgKOP0mUipLQ6OZXu8l4IcAmD2u31EPEy9VcIMvl7SoAaKe8bLXZhYoMaE/in+gcgA==", - # 'crossorigin':"anonymous", - # 'referrerpolicy':"no-referrer" - # }, - # { - # 'rel':"stylesheet", - # 'href':"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/stackoverflow-light.min.css", - # 'integrity':"sha512-RDtnAhiPytLVV3AwzHkGVMVI4szjtSjxxyhDaH3gqdHPIw5qwQld1MVGuMu1EYoof+CaEccrO3zUVb13hQFU/A==", - # 'crossorigin':"anonymous", - # 'referrerpolicy':"no-referrer" - # }, - dbc.themes.FLATLY - ] - app = Dash(__name__, external_stylesheets=css_scripts, external_scripts=js_scripts, suppress_callback_exceptions=True) - app.layout = dbc.Container([ - html.Div([ - html.Div([ - html.H3([ - html.Span("Welcome"), - html.Br(), - html.Span("PerfAnalyzer") - ]), - # html.P("Analyze performance across commit") - ], style={"verticalAlignment": "top"}), - - - - html.Div([ - html.Div([ - html.H6("Provide user and reository name"), - html.Div([ - dcc.Input(id='input-on-submit', value='UK-MAC/CloverLeaf_ref', type='text', style={'width':200}), - html.Button('Get Branches', id='submit-val', n_clicks=0), - html.Div(id='container-button-basic', - children='Enter and press Get Branches') - ]), - ], style={'width': '39%', 'display': 'inline-block'}), - html.Div([ - html.Div([ - html.H6('Branches'), - dcc.Dropdown( - id="branch-multi-option", - options=[ - {'label':i, 'value':i} for i in fetched_branches - ], - placeholder="Select Branches", - clearable=True, - optionHeight=40, - multi=True - ), - # html.Div(id='branch-selection_text', children='Selected branches') - ]) - ], style={'width': '59%', 'display': 'inline-block', 'marginLeft': 0, 'marginRight': 0, 'verticalAlign': 'top'}), - ], - style={ - 'marginTop': 0, - 'marginBottom': 10, - "verticalAlignment": "top" - }), - - html.Div([ - dcc.Dropdown( - id="perf-metric-option", - options=[], - placeholder="Select performance metric to filter", - clearable=True, - optionHeight=40, - multi=True, - style={'marginBottom': 10, 'width': '100%'} - ), - dcc.Loading( - [dcc.Graph(figure=generateParCordChart(perf_data, git_nodes), id='par-coord-graph')], - overlay_style={"visibility":"visible", "opacity": .5, "backgroundColor": "white"}, - custom_spinner=html.H2(["Loading Performance data", dbc.Spinner(color="primary")]), - ), - dcc.Loading( - [dcc.Graph(figure=generateGitChart(perf_data, git_nodes), id='git-graph')], - overlay_style={"visibility":"visible", "opacity": .5, "backgroundColor": "white"}, - custom_spinner=html.H2(["Loading Performance data", dbc.Spinner(color="primary")]), - ), - html.Div( - html.Button('Run performance with selected commits', id='run-perf-commit', n_clicks=0, disabled=True - ), style={'marginTop': 20,'marginBottom': 20}), - html.H4('Selected Commits:'), - dash_table.DataTable(id='selected-second-commits-table', - columns=[ - {'name': 'date(time)', 'id': 'date(time)', 'type': 'text'}, - {'name': 'hash', 'id': 'hash', 'type': 'text'}, - {'name': 'message', 'id': 'message', 'type': 'text'} - ], - filter_action='native', - editable=False, - row_selectable="multi", - row_deletable=True, - style_cell={'textAlign': 'left'}, - style_table={ - 'height': '250px', 'minHeight': '200px', 'maxHeight': '250px', - 'overflow': 'auto', - 'font-size': '12px', - }, - style_data={ - # 'width': '100px', 'minWidth': '100px', - 'height': 'auto', - 'whiteSpace': 'normal', - 'overflow': 'auto', - # 'textOverflow': 'ellipsis', - } - ), - ], style={'width': '100%','marginTop': 0,'marginLeft': 0}), - # html.Div( - # html.Img(src='assets/image.svg', - # style={'marginLeft': 15, 'marginRight': 15, 'marginTop': 30, 'width': 310}) - # ) - # ], style={ - # 'width': '60%', - # 'height': '1000px', - # 'marginLeft': 10, - # 'marginTop': 5, - # 'marginRight': 10 - # }), - html.Div([ - html.Div([ - html.H4("Search any variable, (regex or plaintext):"), - dcc.Input(id='custom-var-search', value='pragma, define', type='text', style={'marginBottom': 10}), - html.Div(id='search-var-info', children='Choose files types to filter'), - dcc.Dropdown( - options={r"\.c":".c", r"\.cc": ".cc", r"\.py":".py", r"\.f90":".f90", r"\.ipynb": ".ipynb"}, - value = [r"\.c", r"\.cc",], - id="filter-file-multi-options", - clearable=True, - optionHeight=40, - multi=True - ), - html.Div(id='file-filter-selection-text', children='Filtered Files', style={'marginBottom': 10}), - html.Button('Search', id='submit-var-search', n_clicks=0, disabled=True), - dash_table.DataTable(id='var-search-table', - columns=[ - {'name': 'variable', 'id': 'var_name', 'type': 'text'}, - {'name': 'file name', 'id': 'file_name', 'type': 'text'}, - # {'name': 'occurance', 'id': 'occ', 'type': 'numeric'} - ], - filter_action='native', - editable=False, - sort_action="native", - row_selectable="multi", - row_deletable=True, - style_cell={'textAlign': 'left'}, - style_table={ - 'height': '250px', 'minHeight': '200px', 'maxHeight': '250px', - 'overflow': 'auto', - 'font-size': '12px', - }, - style_data={ - # 'width': '100px', 'minWidth': '100px', - 'maxWidth': '100px', - 'whiteSpace': 'normal', - 'overflow': 'auto', - # 'textOverflow': 'ellipsis', - } - ), - # html.Div(id='var-results-table', style={'marginTop': 10}), - html.Div(id='code-view', style={'marginTop': 10, - 'height': '1000px', 'maxHeight': '1000px', - 'overflow': 'auto', - }), - ], style={'marginLeft': 0, 'marginTop': 5}), - ], style={ - 'width': '100%', - 'marginLeft': 0, - }), - ], style={ - 'width': '100%', - 'height': '1000px', - 'marginLeft': 10, - 'marginTop': 5, - 'marginRight': 10 - }), - ], - fluid=True, - style={'display': 'flex'}, - className='dashboard-container') - - # app.run_server(debug=True, host = '127.0.0.1') - app.run(debug=True, host = '127.0.0.1') - - -@callback( - # Output('click-data', 'children'), - Input('git-graph', 'clickData')) -def display_click_data(clickData): - print(clickData) - - -@callback( - Output('var-search-table', 'data'), - Input('submit-var-search', 'n_clicks'), - State('custom-var-search', 'value'), - Input('filter-file-multi-options', 'value'), - prevent_initial_call=True -) -def update_var_result_table(n_clicks, search_var, filtered_files): - changed_id = [p['prop_id'] for p in callback_context.triggered][0] - if 'submit-var-search' in changed_id: - - - global perf_runner - global local_cached_data - if 'code_sensing' not in local_cached_data: - local_cached_data['code_sensing'] = dict() - cached_data = local_cached_data['code_sensing'] - - perf_runner.updateGitRepo() - candidate_commit_hash = perf_runner.git_repo.head.object.hexsha - cached_data[candidate_commit_hash] = cached_data.get(candidate_commit_hash, dict()) - - all_var_list = list() - if not search_var: - return list() - for each_var in search_var.split(","): - ev = each_var.rstrip() - if ev not in cached_data[candidate_commit_hash]: - var_dict = code_sensing.recursive_customized_match([ev], filtered_files, perf_runner.current_git_directory) - cached_data[candidate_commit_hash][ev] = cached_data[candidate_commit_hash].get(ev, dict()) - cached_data[candidate_commit_hash][ev].update(var_dict) - - each_hash = candidate_commit_hash - # for each_hash in cached_data: - if ev in cached_data[each_hash]: - for each_var in cached_data[each_hash][ev]: - for each_file in cached_data[each_hash][ev][each_var]: - for f_type in filtered_files: - if re.search(f_type + '$', each_file): - f_list = dict() - f_list['var_name'] = each_var - f_list['hash'] = each_hash - f_list['ev'] = ev - f_list['file_name'] = each_file - f_list['occ'] = len(cached_data[each_hash][ev][each_var][each_file]) - all_var_list.append(f_list) - break - - local_cached_data['code_sensing'] = cached_data - - return all_var_list - return list() - - -@callback( - Output('code-view', "children"), - Input('var-search-table', "derived_virtual_data"), - Input('var-search-table', "derived_virtual_selected_rows"), - Input('selected-second-commits-table', "derived_virtual_data"), - Input('selected-second-commits-table', "derived_virtual_selected_rows"), - prevent_initial_call=True) -def action_on_selected_vars(rows, derived_virtual_selected_rows, selected_commits_row, derived_selected_commits_list): - blank_markdown = dcc.Markdown("", id='actual-source-block') - - mk_data = None - global perf_runner - global local_cached_data - - if 'code_sensing' not in local_cached_data: - return no_update - - cached_data = local_cached_data['code_sensing'] - if cached_data is None: - return no_update - - if len(derived_selected_commits_list) == 0 \ - and (derived_virtual_selected_rows is None or len(derived_virtual_selected_rows) == 0 or len(rows) < derived_virtual_selected_rows[0]): - return blank_markdown - if (len(derived_selected_commits_list) == 1) and \ - (derived_virtual_selected_rows is not None and len(derived_virtual_selected_rows) > 0 and len(rows) >= derived_virtual_selected_rows[0]): - line_padding = 5 - selected_hash = "" - # if derived_virtual_selected_rows is None or len(derived_virtual_selected_rows) == 0 or len(rows) < derived_virtual_selected_rows[0]: - # return blank_markdown - - mk_string = "\n\n Commit: " + rows[derived_virtual_selected_rows[0]]['hash'][:7] + "\n\n" - for i in range(len(derived_virtual_selected_rows)): - mk_data = rows[derived_virtual_selected_rows[i]] - selected_hash = mk_data['hash'] - # selected_hash = selected_commits_row[derived_selected_commits_list[0]]["long_hash"] - if selected_hash in cached_data and mk_data['ev'] in cached_data[selected_hash] and mk_data['var_name'] in cached_data[selected_hash][mk_data['ev']] and mk_data['file_name'] in cached_data[selected_hash][mk_data['ev']][mk_data['var_name']]: - f_lines = cached_data[selected_hash][mk_data['ev']][mk_data['var_name']][mk_data['file_name']] - else: - return dcc.Markdown("##### Selected variable in file does not exist in the selected commit.", id='actual-source-block') - - file_url = perf_runner.current_git_directory + '/' + mk_data['file_name'] - - is_first = True - with open(file_url) as fp: - line_index = 1 - for line in fp: - for lp in range(line_padding): - if line_index-lp in f_lines: - if lp == 0: - if is_first: - is_first = False - else: - mk_string += "```" - mk_string += "\n\n[" + mk_data['file_name'] + " Line:" + str(line_index) + "](https://github.com/" + perf_runner.git_user_repo + "/blob/" + selected_hash + "/" + mk_data['file_name'] + "#L" + str(line_index) + ")\n\n``` " - mk_string += os.path.splitext(mk_data['file_name'])[1] + "\n\n" - mk_string += line + '\n' - line_index = line_index + 1 - if is_first is False: - mk_string += "```" - - mk_component = dcc.Markdown(mk_string, id='actual-source-block') - - return mk_component - elif len(derived_selected_commits_list) > 2: - return dcc.Markdown("##### Please select upto two commits", id='actual-source-block') - elif len(derived_selected_commits_list) == 1: - return dcc.Markdown("##### Please select upto two commits", id='actual-source-block') - - if derived_virtual_selected_rows is not None and len(derived_virtual_selected_rows) > 0 and len(rows) >= derived_virtual_selected_rows[0]: - mk_data = list() - for i in range(len(derived_virtual_selected_rows)): - mk_data.append(rows[derived_virtual_selected_rows[i]]['file_name']) - - # if len(derived_selected_commits_list) > 0 and derived_selected_commits_list[0] not in selected_commits_row: - # return dcc.Markdown("##### Please select two commits to compare", id='actual-source-block') - # if len(derived_selected_commits_list) > 1 and derived_selected_commits_list[1] not in selected_commits_row: - # return dcc.Markdown("##### Please select two commits to compare", id='actual-source-block') - git_repo = getGitRepo(perf_runner.git_user_repo) - first_commit_hash = selected_commits_row[derived_selected_commits_list[0]]["long_hash"] - second_commit_hash = selected_commits_row[derived_selected_commits_list[1]]["long_hash"] - comp_result = git_repo.compare(first_commit_hash, second_commit_hash) - mk_string = "" - if len(comp_result.files) == 0: - comp_result = git_repo.compare(second_commit_hash, first_commit_hash) - mk_string += "\n\n Base: " + second_commit_hash[:7] + "\n\n Head: " + first_commit_hash[:7] + "\n" - else: - mk_string += "\n\n Base: " + first_commit_hash[:7] + "\n\n Head: " + second_commit_hash[:7] + "\n" - - for each_files in comp_result.files: - if each_files.patch is not None: - if mk_data is None or each_files.filename in mk_data: - mk_string += "\n\n[" + each_files.filename + "](https://github.com/" + perf_runner.git_user_repo + "/compare/" + first_commit_hash + ".." + second_commit_hash + ")\n\n" - mk_string += "```diff\n\n" - mk_string += each_files.patch - mk_string += "\n```\n" - - mk_component = dcc.Markdown(mk_string, id='actual-source-block') - - return mk_component - -@callback( - Output('search-var-info', 'children'), - Input('submit-var-search', 'n_clicks'), - State('custom-var-search', 'value'), - prevent_initial_call=True -) -def update_search_var_output_text(n_clicks, search_var): - global perf_runner - results = parse_output_analyzer.get_all_db_data(perf_runner.testname, perf_runner.current_working_directory) - if results is not None and len(results) > 0: - pass - return 'Searched vars: {}'.format( - search_var - ) - - -@callback( - Output('container-button-basic', 'children'), - Output('submit-var-search','disabled'), - Input('submit-val', 'n_clicks'), - State('input-on-submit', 'value'), - prevent_initial_call=True -) -def update_branch_output_text(n_clicks, value): - repo_name = value - global perf_runner - perf_runner.initGitRepo(repo_name) - return ['Fetched branches for {}'.format( - value - ), False] - -@callback( - Output('branch-multi-option', 'options'), - Input('submit-val', 'n_clicks'), - State('input-on-submit', 'value'), - prevent_initial_call=True -) -def update_branch_output_lists(n_clicks, value): - global fetched_branches - global repo_name - repo_name = value - repo = getGitRepo(repo_name) - fetched_branches = repo.get_branches() - return [br.name for br in fetched_branches] - -def get_browsable_perf_metric(df): - if df is None: - return list() - column_list = list() - for column in df: - if column == 'testname' or column.startswith("git_") or 'min' in column or 'max' in column or 'x_cells' in column or 'y_cells' in column: - continue - column_list.append(column) - return column_list - -def find_interesting_perf_metric(df): - if df is None: - return list() - for column in df: - if column == 'testname' or column.startswith("git_") or 'min' in column or 'max' in column or 'x_cells' in column or 'y_cells' in column: - continue - df[column] = pd.to_numeric(df[column].fillna(value=np.nan), errors='coerce') - another = df.select_dtypes(include='number').std() > 0.03# 0.015 - return list(another[another].index) - -@callback( - Output('git-graph', 'figure'), - Output('par-coord-graph', 'figure'), - Output('perf-metric-option', 'options'), - Output('perf-metric-option', 'value'), - Input('input-on-submit', 'value'), - Input('branch-multi-option', 'value'), - Input('git-graph', 'selectedData'), - Input('run-perf-commit', 'n_clicks'), - State('custom-var-search', 'value'), - Input('filter-file-multi-options', 'value'), - State('var-search-table', "derived_virtual_data"), - State('var-search-table', "derived_virtual_selected_rows"), - Input('perf-metric-option', 'value'), - prevent_initial_call=True -) -def update_branch_selection_output(repo_name, value, selectedData, n_clicks, search_var, filtered_files, rows, derived_virtual_selected_rows, perf_list): - changed_id = [p['prop_id'] for p in callback_context.triggered][0] - global perf_runner - if 'branch-multi-option' in changed_id or 'perf-metric-option' in changed_id: - selected_branches = value - git_nodes = getGitGraph(repo_name, selected_branches) - - results = parse_output_analyzer.get_all_db_data(perf_runner.testname, perf_runner.current_working_directory) - sorted_df = None - if results is not None and len(results) > 0: - sorted_df = results.sort_values(by=['git_committed_date'], ascending=True) - pfl = perf_list - if perf_list is None or len(perf_list) == 0: - pfl = find_interesting_perf_metric(sorted_df) - # return [generateGitChart(sorted_df, git_nodes,perf_filter=pfl), get_browsable_perf_metric(sorted_df), pfl] - return [generateGitChart(sorted_df, git_nodes,perf_filter=pfl), - # no_update, - generateParCordChart(sorted_df, git_nodes,perf_filter=pfl), - get_browsable_perf_metric(sorted_df), - pfl] - - elif 'run-perf-commit' in changed_id: - if selectedData is None: - [no_update, no_update] - dss = pd.DataFrame(selectedData['points']) - - msg_list = [x[0] for x in dss['customdata']] - hash_list = [x[1] for x in dss['customdata']] - - if 'customdata' not in dss: - [no_update, no_update] - - c_df = pd.DataFrame({ - 'date(time)':dss['x'].to_list(), - 'hash':hash_list, - 'message': msg_list - }) - - table_df = c_df.dropna().drop_duplicates(subset=['hash']).sort_values(by=['date(time)'], ascending=True) - global local_cached_data - if 'code_sensing' not in local_cached_data: - local_cached_data['code_sensing'] = dict() - cached_data = local_cached_data['code_sensing'] - - for ind in table_df.index: - candidate_commit_hash = table_df['hash'][ind] - - if parse_output_analyzer.test_artifact_query(perf_runner.testname, perf_runner.current_working_directory, candidate_commit_hash): - try: - subprocess.run('cd ' + perf_runner.current_git_directory + ' && git checkout -f ' + candidate_commit_hash, shell=True) - except subprocess.CalledProcessError as cpe: - print(cpe.output) - finally: - print("checkout done") - # perf_runner.git_repo.git.checkout(force=True,hash=candidate_commit_hash) - else: - my_env = os.environ.copy() - my_env['CANDIDATE_COMMIT_HASH'] = candidate_commit_hash - my_env['SOURCE_BASE_DIRECTORY'] = perf_runner.current_git_directory - # my_env["PATH"] = f"/Users/ssakin/Softwares/anaconda3/envs/cdsi/bin:{my_env['PATH']}" - try: - command = ['cd /Users/ssakin/projects/dsi/tools/perf_analyzer && source runner_script.sh'] - subprocess.run(command, env=my_env, shell=True) - except subprocess.CalledProcessError as cpe: - print(cpe.output) - finally: - print("final done") - - data = parse_output_analyzer.parse_sim_output_file(perf_runner.testname, perf_runner.current_git_directory) - tau_data = parse_output_analyzer.parse_tau_output_file(perf_runner.testname, perf_runner.current_git_directory) - data.update(tau_data) - parse_output_analyzer.add_output_to_dsi(data, perf_runner.testname, perf_runner.current_working_directory) - - cached_data[candidate_commit_hash] = cached_data.get(candidate_commit_hash, dict()) - if search_var: - for each_var in search_var.split(","): - ev = each_var.rstrip() - if ev not in cached_data[candidate_commit_hash]: - var_dict = code_sensing.recursive_customized_match([ev], filtered_files, perf_runner.current_git_directory) - cached_data[candidate_commit_hash][ev] = cached_data[candidate_commit_hash].get(ev, dict()) - cached_data[candidate_commit_hash][ev].update(var_dict) - - - print('got chart updates') - local_cached_data['code_sensing'] = cached_data - results = parse_output_analyzer.get_all_db_data(perf_runner.testname, perf_runner.current_working_directory) - if results is not None and len(results) > 0 and value is not None: - sorted_df = results.sort_values(by=['git_committed_date'], ascending=True) - selected_branches = value - git_nodes = getGitGraph(repo_name, selected_branches) - mk_data = None - if derived_virtual_selected_rows is not None and len(derived_virtual_selected_rows) > 0 and len(rows) >= derived_virtual_selected_rows[0]: - mk_data = rows[derived_virtual_selected_rows[0]] - pfl = perf_list - if perf_list is None or len(perf_list) == 0: - pfl = find_interesting_perf_metric(sorted_df) - return [generateGitChart(sorted_df, git_nodes, mk_data, pfl), - # no_update, - generateParCordChart(sorted_df, git_nodes,perf_filter=pfl), - get_browsable_perf_metric(sorted_df), - pfl] - return [no_update, no_update, no_update, no_update] - return [no_update, no_update, no_update, no_update] - - -@callback( - Output('run-perf-commit','disabled'), - Output('selected-second-commits-table', 'data'), - Input('git-graph', 'selectedData') -) -def update_git_selection(selection): - if selection is None: - return [True, list()] - - dss = pd.DataFrame(selection['points']) - - if dss is None or 'customdata' not in dss: - return [True, no_update] - - msg_list = [x[0] for x in dss['customdata']] - hash_list = [x[1][:7] for x in dss['customdata']] - long_hash_list = [x[1] for x in dss['customdata']] - - c_df = pd.DataFrame({ - 'date(time)':dss['x'].to_list(), - 'hash':hash_list, - 'long_hash':long_hash_list, - 'message': msg_list - }) - table_df = c_df.dropna().drop_duplicates(subset=['hash']).sort_values(by=['date(time)'], ascending=True) - c_df_list = list() - for ea_val in table_df.values.tolist(): - c_dict = { - 'date(time)':ea_val[0], - 'hash':ea_val[1], - 'long_hash':ea_val[2], - 'message': ea_val[3] - } - c_df_list.append(c_dict) - return [False, c_df_list] - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - # parser.add_argument('--testname', help='the test name') - # parser.add_argument('--gitdir', help='the git directory') - # args = parser.parse_args() - # if args.gitdir is None: - # parser.print_help() - # sys.exit(0) - # git_repo = git.Repo(args.gitdir) - global perf_runner - perf_runner = PerfRunner() - - initAndLoadCachedData(perf_runner.current_working_directory) - # df = pd.read_csv("clover_random_test.csv") - # sorted_df = df.sort_values(by=['git_committed_date'], ascending=True) - # git_nodes = getGitGraph(sorted_df["git_repo_name"][0],[]) - main(None, None) \ No newline at end of file diff --git a/examples/perf_analyzer/initate_git_workflow_through_issue.py b/examples/perf_analyzer/initate_git_workflow_through_issue.py deleted file mode 100644 index 8b76520b..00000000 --- a/examples/perf_analyzer/initate_git_workflow_through_issue.py +++ /dev/null @@ -1,26 +0,0 @@ - -import argparse -from github import Github - - - - -def getGitRepo(user_repo): - repo = Github().get_repo(user_repo) - return repo - -def testGitIsssue(): - user_repo = "sayefsakin/flycatcher" - repo = getGitRepo(user_repo) - repo.create_issue(title="This is a new issue") - issue = repo.get_issue(number=1) - issue.create_comment("This") - print(issue.title) - -def main(): - testGitIsssue() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - main() \ No newline at end of file diff --git a/examples/perf_analyzer/parse_clover_output.py b/examples/perf_analyzer/parse_clover_output.py deleted file mode 100644 index ecbc0eab..00000000 --- a/examples/perf_analyzer/parse_clover_output.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python3 - -""" -Parses the output from CloverLeaf runs and creates a csv file -""" - -import argparse -import sys -import re -import git -# import pandas as pd -# from dsi.plugins.collection_reader import Dict -# from dsi.backends.sqlite import Sqlite, DataType -from dsi.dsi import DSI - -def get_repo_and_name_from_url(url: str): - last_colon_index = url.rfind(":") - last_suffix_index = url.rfind(".git") - if last_suffix_index < 0: - last_suffix_index = len(url) - - if last_colon_index < 0 or last_suffix_index <= last_colon_index: - raise Exception("Badly formatted url {}".format(url)) - - return url[last_colon_index + 1:last_suffix_index] - -def get_git_repo(gitdir): - repo = git.Repo(gitdir) - # print(repo.head.object.committer) - # print(repo.head.object.committed_datetime.strftime("%Y-%m-%d")) - # print(get_repo_and_name_from_url(repo.remotes.origin.url)) - return repo - -def add_output_to_csv_file(data, testname): - """ The csv file is created and written to disk """ - with open('clover_' + testname + '.csv', 'a+') as clover_out: - header = "" - row = "" - for key, val in data.items(): - header += key + "," - row += str(val) + "," - - header = header.rstrip(',') - row = row.rstrip(',') - - if clover_out.tell() == 0: - clover_out.write(header + "\n") - - clover_out.write(row + "\n") - -# def add_non_existing_columns(data, test_name, db_base_dir): -# dbpath = db_base_dir + "/clover_" + test_name + ".db" -# store = Sqlite(dbpath) -# data_type = DataType() -# data_type.name = "TABLENAME" - -# query = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + data_type.name + "'" -# result = store.sqlquery(query) -# if len(result) == 0: -# return - -# str_query = "PRAGMA table_info( " + data_type.name + " );" -# result = store.sqlquery(str_query) -# art_list = [tup[1] for tup in result] -# for key in data: -# if key not in art_list: -# print("key not exist: ", key) -# str_query = "ALTER TABLE " + data_type.name + " ADD " + key + " VARCHAR DEFAULT None" -# result = store.sqlquery(str_query) -# print(key, " added") -# store.close() - - -def add_output_to_dsi(data, test_name, db_base_dir): - dbpath = db_base_dir + '/clover_' + test_name + '.db' - - store = DSI(dbpath) - store.read(data, "Collections", table_name="TABLENAME") - store.close() - - - # dsi_dict = Dict(data) - # dsi_dict.add_rows() - # # print(dsi_dict.collections) - - # add_non_existing_columns(data, test_name, db_base_dir) - - # store = Sqlite(dbpath) - # # store.types.name = test_name - # store.put_artifacts(dsi_dict.collections[0], isVerbose=False) - # store.close() - - -""" -Performs a sample query on the DSI db -""" -def test_artifact_query(test_name, db_base_dir, git_hash): - dbpath = db_base_dir + "/clover_" + test_name + ".db" - table_name = "TABLENAME" - - store = DSI(dbpath) - result = store.query(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'", True) - if result.empty: - return - - result = store.query(f"SELECT count(*) as cn FROM {table_name} WHERE git_hash LIKE '{git_hash}%'", True) - store.close() - if not result.empty and result.iloc[0, 0] > 0: - print("found") - return True - else: - print("not found") - return False - - # store = Sqlite(dbpath) - # _ = store.get_artifact_list(isVerbose=False) - # data_type = DataType() - # data_type.name = "TABLENAME" - # query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{data_type.name}'" - # result = store.sqlquery(query) - # if len(result) == 0: - # return False - # query = "SELECT count(*) as cn FROM " + str(data_type.name) + " WHERE git_hash LIKE '" + git_hash + "%'" - # print("Running Query", query) - # result = store.sqlquery(query) - # store.close() - # if len(result) > 0 and result[0][0] > 0: - # print("found") - # return True - # else: - # print("not found") - # return False - # store.export_csv_query(query, "clover_query.csv") - - -""" -Performs a sample query on the DSI db -""" -def get_all_db_data(test_name, db_base_dir): - dbpath = db_base_dir + "/clover_" + test_name + ".db" - table_name = "TABLENAME" - - store = DSI(dbpath) - result = store.query(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'", True) - if result.empty: - return - - result = store.query(f"SELECT * FROM {table_name}", True) - store.close() - return result - - # store = Sqlite(dbpath) - # _ = store.get_artifact_list(isVerbose=False) - # data_type = DataType() - # data_type.name = "TABLENAME" - # query = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + data_type.name + "'" - # result = store.sqlquery(query) - # if len(result) == 0: - # return None - # query = "SELECT * FROM " + str(data_type.name) - # print("Running Query", query) - # result = pd.read_sql(query, store.con) - # # result = store.sqlquery(query) - # store.close() - # return result - - -def process_keys_for_sqlite(key): - return key.replace(" ", "_").lower() - - -def parse_clover_output_file(testname, git_dir): - git_repo = get_git_repo(git_dir) - data = {} - data['testname'] = [testname] - clover_output = git_dir + "/" - if git_repo: - data['git_hash'] = [git_repo.head.object.hexsha] - data['git_committer'] = [git_repo.head.object.committer.email] - data['git_committed_date'] = [git_repo.head.object.committed_datetime.strftime("%Y-%m-%d %H:%M:%S")] - data['git_repo_name'] = [get_repo_and_name_from_url(git_repo.remotes.origin.url)] - clover_output = clover_output + "clover.out" - # print(clover_output) - else: - raise Exception("Git repo not found") - - with open(clover_output, 'r') as slurmout: - for line in slurmout: - if "Clover Version" in line: - match = re.match(r'Clover Version\s+(\d+.\d+)', line) - version = match.group(1) - data['version'] = [version] - elif "Task Count" in line: - match = re.match(r'\s+Task Count\s+(\d+)', line) - version = match.group(1) - data['Task_Count'] = [version] - elif "Thread Count" in line: - match = re.match(r'\s+Thread Count:\s+(\d+)', line) - version = match.group(1) - data['Thread_Count'] = [version] - elif "=" in line: - # reading input data - match = re.match(r'\s+(\w+)=(\d+.?\d+)', line) - if match: - pro_key = process_keys_for_sqlite(match.group(1)) - pro_value = match.group(2) - data[pro_key] = [pro_value] - else: - # reading profiler output - match = re.match(r'(\w+(\s?\w+)*)\s+:\s+(\d+.\d+)\s+(\d+.\d+)', line) - if match: - pro_key = process_keys_for_sqlite(match.group(1)) - pro_value = match.group(3) - data[pro_key] = [pro_value] - # print(data) - return data - - -def parse_tau_output_file(testname, git_dir): - git_repo = get_git_repo(git_dir) - data = {} - data['testname'] = [testname] - clover_output = git_dir + "/" - if git_repo: - data['git_hash'] = [git_repo.head.object.hexsha] - data['git_committer'] = [git_repo.head.object.committer.email] - data['git_committed_date'] = [git_repo.head.object.committed_datetime.strftime("%Y-%m-%d %H:%M:%S")] - data['git_repo_name'] = [get_repo_and_name_from_url(git_repo.remotes.origin.url)] - clover_output = clover_output + "tau_results" - # print(clover_output) - else: - raise Exception("Git repo not found") - - do_parse = False - with open(clover_output, 'r') as slurmout: - for line in slurmout: - if 'total' in line: - do_parse = True - elif 'mean' in line: - do_parse = False - if do_parse is True: - if "MPI_" in line: - match = re.match(r'\s+(\d+\.\d+)\s+([\d,\.]+)\s+([\d,\.]+)\s+([\d,\.]+)\s+([\d,\.]+)\s+([\d,\.]+)\s+(MPI_\w+)\(\)\s+', line) - # data[match.group(7) + '_time'] = [match.group(1)] - data[match.group(7) + '_esec'] = [float(match.group(2))/1000] - data[match.group(7) + '_isec'] = [float(match.group(3))/1000] - # data[match.group(7) + '_calls'] = [match.group(4)] - # data[match.group(7) + '_subs'] = [match.group(5)] - # data[match.group(7) + '_iusec_p_call'] = [match.group(6)] - # print(data) - return data - - - - -def main(): - """ A testname argument is required """ - parser = argparse.ArgumentParser() - parser.add_argument('--testname', help='the test name') - parser.add_argument('--gitdir', help='the git directory') - args = parser.parse_args() - # testname = "temp_test" - testname = args.testname - if testname is None: - parser.print_help() - sys.exit(0) - - # data = parse_clover_output_file(testname, args.gitdir) - data = parse_tau_output_file(testname, args.gitdir) - # add_output_to_csv_file(data, testname) - add_output_to_dsi(data, testname) - - # test_artifact_query(testname) - -if __name__ == '__main__': - main() - - diff --git a/examples/perf_analyzer/parse_output_analyzer.py b/examples/perf_analyzer/parse_output_analyzer.py deleted file mode 100644 index b29480be..00000000 --- a/examples/perf_analyzer/parse_output_analyzer.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python3 - -""" -Parses the output from CloverLeaf runs and creates a csv file -""" - -import re -import git -# import pandas as pd -# from dsi.plugins.collection_reader import Dict -# from dsi.backends.sqlite import Sqlite, DataType -from dsi.dsi import DSI - -def get_repo_and_name_from_url(url: str): - last_colon_index = url.rfind(":") - last_suffix_index = url.rfind(".git") - if last_suffix_index < 0: - last_suffix_index = len(url) - - if last_colon_index < 0 or last_suffix_index <= last_colon_index: - raise Exception("Badly formatted url {}".format(url)) - - return url[last_colon_index + 1:last_suffix_index] - -def get_git_repo(gitdir): - repo = git.Repo(gitdir) - # print(repo.head.object.committer) - # print(repo.head.object.committed_datetime.strftime("%Y-%m-%d")) - # print(get_repo_and_name_from_url(repo.remotes.origin.url)) - return repo - -def add_output_to_csv_file(data, testname): - """ The csv file is created and written to disk """ - with open('clover_' + testname + '.csv', 'a+') as clover_out: - header = "" - row = "" - for key, val in data.items(): - header += key + "," - row += str(val) + "," - - header = header.rstrip(',') - row = row.rstrip(',') - - if clover_out.tell() == 0: - clover_out.write(header + "\n") - - clover_out.write(row + "\n") - -# def add_non_existing_columns(data, test_name, db_base_dir): -# dbpath = db_base_dir + "/clover_" + test_name + ".db" -# store = Sqlite(dbpath) -# data_type = DataType() -# data_type.name = "TABLENAME" - -# query = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + data_type.name + "'" -# result = store.sqlquery(query) -# if len(result) == 0: -# return - -# str_query = "PRAGMA table_info( " + data_type.name + " );" -# result = store.sqlquery(str_query) -# art_list = [tup[1] for tup in result] -# for key in data: -# if key not in art_list: -# print("key not exist: ", key) -# str_query = "ALTER TABLE " + data_type.name + " ADD " + key + " VARCHAR DEFAULT None" -# result = store.sqlquery(str_query) -# print(key, " added") -# store.close() - - -def add_output_to_dsi(data, test_name, db_base_dir): - dbpath = db_base_dir + '/clover_' + test_name + '.db' - - store = DSI(dbpath) - store.read(data, "Collections", table_name="TABLENAME") - store.close() - - # dsi_dict = Dict(data) - # dsi_dict.add_rows() - # # print(dsi_dict.collections) - - # add_non_existing_columns(data, test_name, db_base_dir) - - # store = Sqlite(dbpath) - # # store.types.name = test_name - # store.put_artifacts(dsi_dict.collections[0], isVerbose=False) - # store.close() - - -""" -Performs a sample query on the DSI db -""" -def test_artifact_query(test_name, db_base_dir, git_hash): - dbpath = db_base_dir + "/clover_" + test_name + ".db" - table_name = "TABLENAME" - - store = DSI(dbpath) - result = store.query(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'", True) - if result.empty: - return - - result = store.query(f"SELECT count(*) as cn FROM {table_name} WHERE git_hash LIKE '{git_hash}%'", True) - store.close() - if not result.empty and result.iloc[0, 0] > 0: - print("found") - return True - else: - print("not found") - return False - - # store = Sqlite(dbpath) - # _ = store.get_artifact_list(isVerbose=False) - # data_type = DataType() - # data_type.name = "TABLENAME" - # query = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + data_type.name + "'" - # result = store.sqlquery(query) - # if len(result) == 0: - # return False - # query = "SELECT count(*) as cn FROM " + str(data_type.name) + " WHERE git_hash LIKE '" + git_hash + "%'" - # print("Running Query", query) - # result = store.sqlquery(query) - # store.close() - # if len(result) > 0 and result[0][0] > 0: - # print("found") - # return True - # else: - # print("not found") - # return False - # store.export_csv_query(query, "clover_query.csv") - - -""" -Performs a sample query on the DSI db -""" -def get_all_db_data(test_name, db_base_dir): - dbpath = db_base_dir + "/clover_" + test_name + ".db" - table_name = "TABLENAME" - - store = DSI(dbpath) - result = store.query(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'", True) - if result.empty: - return - - result = store.query(f"SELECT * FROM {table_name}", True) - store.close() - return result - - # store = Sqlite(dbpath) - # _ = store.get_artifact_list(isVerbose=False) - # data_type = DataType() - # data_type.name = "TABLENAME" - # query = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + data_type.name + "'" - # result = store.sqlquery(query) - # if len(result) == 0: - # return None - # query = "SELECT * FROM " + str(data_type.name) - # print("Running Query", query) - # result = pd.read_sql(query, store.con) - # # result = store.sqlquery(query) - # store.close() - # return result - - -def process_keys_for_sqlite(key): - return key.replace(" ", "_").lower() - -def parse_clover_output_file(testname, git_dir): - git_repo = get_git_repo(git_dir) - data = {} - data['testname'] = [testname] - clover_output = git_dir + "/" - if git_repo: - data['git_hash'] = [git_repo.head.object.hexsha] - data['git_committer'] = [git_repo.head.object.committer.email] - data['git_committed_date'] = [git_repo.head.object.committed_datetime.strftime("%Y-%m-%d %H:%M:%S")] - data['git_repo_name'] = [get_repo_and_name_from_url(git_repo.remotes.origin.url)] - clover_output = clover_output + "clover.out" - # print(clover_output) - else: - raise Exception("Git repo not found") - - with open(clover_output, 'r') as slurmout: - for line in slurmout: - if "Clover Version" in line: - match = re.match(r'Clover Version\s+(\d+.\d+)', line) - version = match.group(1) - data['version'] = [version] - elif "Task Count" in line: - match = re.match(r'\s+Task Count\s+(\d+)', line) - version = match.group(1) - data['Task_Count'] = [version] - elif "Thread Count" in line: - match = re.match(r'\s+Thread Count:\s+(\d+)', line) - version = match.group(1) - data['Thread_Count'] = [version] - elif "=" in line: - # reading input data - match = re.match(r'\s+(\w+)=(\d+.?\d+)', line) - if match: - pro_key = process_keys_for_sqlite(match.group(1)) - pro_value = match.group(2) - data[pro_key] = [pro_value] - else: - # reading profiler output - match = re.match(r'(\w+(\s?\w+)*)\s+:\s+(\d+.\d+)\s+(\d+.\d+)', line) - if match: - pro_key = process_keys_for_sqlite(match.group(1)) - pro_value = match.group(3) - data[pro_key] = [pro_value] - # print(data) - return data - -def parse_sim_output_file(testname, git_dir): - return parse_clover_output_file(testname, git_dir) - -def parse_tau_output_file(testname, git_dir): - git_repo = get_git_repo(git_dir) - data = {} - data['testname'] = [testname] - tau_output_file = git_dir + "/" + "tau_results" - if git_repo: - data['git_hash'] = [git_repo.head.object.hexsha] - data['git_committer'] = [git_repo.head.object.committer.email] - data['git_committed_date'] = [git_repo.head.object.committed_datetime.strftime("%Y-%m-%d %H:%M:%S")] - data['git_repo_name'] = [get_repo_and_name_from_url(git_repo.remotes.origin.url)] - else: - raise Exception("Git repo not found") - - do_parse = False - core_parse = False - nct_text = "" - with open(tau_output_file, 'r') as slurmout: - for line in slurmout: - core_parse_match = re.match(r'(NODE\ )(\d+)(;CONTEXT\ )(\d+)(;THREAD\ )(\d+)\:', line) - core_false_match = re.match(r'(USER EVENTS Profile \:).*', line) - total_match = re.match(r'(FUNCTION SUMMARY \(total\)\:).*', line) - mean_match = re.match(r'(FUNCTION SUMMARY \(mean\)\:).*', line) - - if core_parse_match: - core_parse = True - nct_text = "_" + core_parse_match.group(2)# + "_" + core_parse_match.group(4) + "_" + core_parse_match.group(6) - elif core_parse and core_false_match: - core_parse = False - nct_text = "" - - - if total_match: - do_parse = True - nct_text = "" - elif mean_match: - do_parse = False - - if do_parse is True or core_parse is True: - if "MPI_" in line: - print(line) - match = re.match(r'\s+(\d+\.\d+)\s+([\d,\.]+)\s+([\d,\.]+)\s+([\d,\.]+)\s+([\d,\.]+)\s+([\d,\.]+)\s+(MPI_\w+)\(\)\s+', line) - # data[match.group(7) + '_time'] = [match.group(1)] - data[match.group(7) + nct_text + '_esec'] = [float(match.group(2))/1000] - data[match.group(7) + nct_text + '_isec'] = [float(match.group(3))/1000] - # data[match.group(7) + '_calls'] = [match.group(4)] - # data[match.group(7) + '_subs'] = [match.group(5)] - # data[match.group(7) + '_iusec_p_call'] = [match.group(6)] - # print(data) - return data - - - - -def main(): - # """ A testname argument is required """ - # parser = argparse.ArgumentParser() - # parser.add_argument('--testname', help='the test name') - # parser.add_argument('--gitdir', help='the git directory') - # args = parser.parse_args() - # # testname = "temp_test" - # testname = args.testname - # if testname is None: - # parser.print_help() - # sys.exit(0) - - testname = "test" - gitdir = "/Users/ssakin/projects/CloverLeaf/CloverLeaf_ref/tauProfiles" - # data = parse_clover_output_file(testname, args.gitdir) - data = parse_tau_output_file(testname, gitdir) - # print(data) - # add_output_to_csv_file(data, testname) - add_output_to_dsi(data, testname) - - # test_artifact_query(testname) - -if __name__ == '__main__': - main() - - diff --git a/examples/perf_analyzer/perf_hist_chart.ipynb b/examples/perf_analyzer/perf_hist_chart.ipynb deleted file mode 100644 index eb0f9db3..00000000 --- a/examples/perf_analyzer/perf_hist_chart.ipynb +++ /dev/null @@ -1,1520 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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cfjmm29Q8j7Jhw4dQseOHeHo6IguXbrg+PHj8jadToeAgAB8/vnnJvscO3YMkiTh4sWLAPT1os899xwaNWoEd3d33H///Thx4oTJPps2bUKXLl3g6OiIBg0aYNiwYQCAfv364dKlS3j11VflsTKMYcnTSStWrECLFi3g4OCA0NBQfPvttybbJUnC119/jcceewzOzs5o2bIlNm3aVOEY1Rqiiu7cuSOOHz8ujh8/LgCIJUuWiOPHj4tLly6JgoIC8a9//UsEBASIqKgokZycLD/y8vIq1X96eroAINLT06saGlGdkHHrhlg8fLD4aMQQkZuVaetw6o2cnBwRExMjcnJy5HU6nU7k5xbW+EOn01U67vHjx4uIiAixZMkS8cADD8jrH3jgAbF06VIREREhxo8fL6+fMmWKaNy4sdiyZYs4ffq0GD9+vPDy8hK3bt0SQgixY8cOAUDce++9Yu/eveLYsWMiJCRE9O3bVzz00EPi2LFjYvfu3cLHx0d88MEHcr//93//J1q3bi3++OMPceHCBREZGSk0Go3YuXOnSb/du3cXO3fuFKdPnxb33Xef6NmzpxBCiOzsbDFt2jTRpk0b+X0jOztbxMfHCwDi+PHj8rFSU1MFALFjx467itmcjIwM4eLiIk6dOiUKCwuFr6+v+Pvvv+XtmZmZomHDhmLEiBHi1KlTYvPmzaJ58+YmMU6bNk307t3bpN9p06aJHj16CCH031e9evUSQ4YMEYcPHxZxcXFi2rRpwsfHR/5/+PXXX4VSqRSzZ88WMTExIioqSrz33ntCCCFu3bolAgICxPz58+WxEkKIyMhI4eHhIR9z/fr1Qq1Wi08//VScPXtWfPTRR0KpVJq8HgAiICBArFmzRpw7d05MmTJFuLq6ynGYY+5nRQjbvH9X+SPWR44cQf/+/eXl1157DQAwfvx4zJ07V87gSp7X3LFjB/r161fVwxHVO27eDeDp64+068m4ciYGzTt1tXVI9VZhvg5fTt1V48d9bnlfqDXKKu0zbtw4zJgxQ5652LdvH3744Qd5tgLQX+drxYoVWLVqlXypjK+++grbtm3DypUrMX36dLntu+++i169egEAJk2ahBkzZuDChQto3rw5AOCJJ57Ajh078OabbyIrKwtLlizB33//jR49egAAmjdvjr179+KLL75A37595X7fe+89efmtt97C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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import pandas as pd\n", - "\n", - "df = pd.read_csv(\"clover_random_test.csv\")\n", - "df.head()\n", - "int_cols = {\"PdV\":'mean', \"Cell Advection\":'mean', \"MPI Halo Exchange\":'mean', \"Self Halo Exchange\":'mean', \"Momentum Advection\":'mean', \"Total\":'mean'}\n", - "# print(df.groupby('git_committed_date')[\"Total\"].mean())\n", - "sorted_df = df.groupby('git_committed_date').agg(int_cols)\n", - "commit_dates = pd.to_datetime(sorted_df.index.to_list()).strftime(\"%b %d, %Y\")\n", - "sorted_df.plot()" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PdV\n", - "Cell Advection\n", - "MPI Halo Exchange\n", - "Self Halo Exchange\n", - "Momentum Advection\n", - "Total\n" - ] - } - ], - "source": [ - "\n", - "for i in sorted_df.columns:\n", - " col_name = i\n", - " print(col_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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xx389ddfbrMz16lTh8cff5yff/7Z0+F51Pjx41EUhfvuu6/YuilTpqAoCuPHjy+2vaIomM1mGjVqxLRp0/RZt48cOYKiKMTFxZV6zNJO7gsWLCA4OPgS31H5Fbyf8x+ff/75ZY9FiKpS0GcmSJIZIS7Iox2AMzMzOXjwoL6ckJBAXFwcISEh1K9fn9q1a7ttbzabiYyMpHnz5pc71GonOjqazz//nDfeeEOfpDE3N5clS5ZQv379YtsPGTKE+fPnY7fb+f3337n77rvJysrSJ6asiebPn8+QIUPcyjyRWAlRVdJSkgHpMyPExXi0ZWbr1q106NCBDh06APDoo4/SoUMHnn32WU+GVSN07NiR+vXrs3TpUr1s6dKlREdH659nURaLhcjISKKjoxk7dizjxo274CCFFXXo0CGGDx9OREQE/v7+dOnShV9//fWC+xw7dozhw4fj7+9PYGAgo0ePJjk5+aLHCg4OJjIy0u1RMBHixIkTadu2rX5nm91up1OnTowbN07ff8OGDfTp0wdfX19q1arF4MGDSU1N1derqsr06dMJCQkhMjKS559/3u34r7/+Om3atMHPz4/o6GimTJlCZmamvr6g1ernn3+mZcuW+Pv7M2TIEBITE/VtHA4HDz30EMHBwdSuXZsnnniCu+66ixEjRujbaJrG7NmzadSoET4+PrRr146vv/76op+PqNls2VnkZrjuxgwKL/9M90JcTTyazPTt2xdN04o9FixYUOL2R44cuWz9GLLt2eV+OFSHvr9DdZBtzybXkXvReitqwoQJzJ8/X1/++OOPmThxYpn29fHxwW63V/jYpcnMzOSGG27g119/ZceOHQwePJhhw4Zx7NixErfXNI0RI0Zw7tw51q5dy8qVKzl06BC33nrrJcXx9ttvk5WVxZNPPgnAjBkzOHPmDP/5z38AiIuLY8CAAcTGxvLHH3+wfv16hg0bhtPp1OtYuHAhfn5+bNq0idmzZ/Piiy+ycuVKfb3BYODtt9/m77//ZuHChfz2229Mnz7dLY7s7GzmzJnDp59+yrp16zh27BjTpk3T18+aNYtFixYxf/58NmzYQHp6erEk85lnnmH+/PnMnTuX3bt388gjj3D77bezdu3aS/qMRPVW0F/GNygYLx9fD0cjRDWnXeGsVqsGaFartdi6nJwcbc+ePVpOTk6xda0XtC7346eE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eXt7az169NCeeuopbefOnfr6VatWaYGBgVpubq7bfo0bN9Y++OADTdM07bnnntPatWunr/v222+1i/06JCQkaIDm4+Oj+fn5uT0MBoM2derUUvfdvHmzBmgZGRmapmna6tWrNUBLTU3VNE3Txo4dqw0aNMhtn8cff1xr1apVqXXOnz9fA4rFcn49U6ZM0Zo1a6aNGzdOa926tf7zTk9P1ywWi/bf//73gu/3o48+0st2796tAVp8fHypcd1www3aY489pi/36dNH69Wrl9s2Xbp00Z544glN0zTtxx9/1Ewmk5aYmKivX7lypQZo3377raZpmjZv3jytefPmmqqq+jY2m03z8fHRfv7551JjqQwX+jsRlevM8WPanNFDtbfvusXtZy3E1ehC5+/zSZ+ZGmjUqFEMHTqU33//nT/++IOffvqJ2bNn89FHHzF+/Hi2bdtGZmYmtWvXdtsvJydH7xtzKb744otiszqPGzfObXnHjh08//zzxMXFce7cOVRVBeDYsWO0atWqWJ3x8fEMHz7craxnz568+eabOJ1OjEZjibEEBASwfft2tzIfHx+35Tlz5tC6dWu+/PJLtm7dqncQjo+Px2azMWDAgAu+37Zt2+qvo6JcnTJTUlJo0aIFTqeTV155hS+++IKTJ09is9mw2Wz4+fmVWkdBPQWX3fbt20d0dDSRkYXfxLt27eq2/bZt2zh48CABAQFu5bm5uZXyMxXVQ0F/meCIKOn7JUQ5SDJTiubbt5V7H6XIAFcBAwe66jC4X8lrsurXS44NwNvbm0GDBjFo0CCeffZZ7r77bp577jnGjx+PqqpERUWxZs2aYvsFBwdf8rGjo6Np0qSJW1nRBCIrK4vrrruO6667js8++4ywsDCOHTvG4MGDS70komlasX+8NU27aCwGg6FYLOc7fPgwp06dQlVVjh49qicW5yc9pTGbzfrrghgLkrPXXnuNN954gzfffJM2bdrg5+fHww8/XOx9Fq2joJ6COkp67+dTVZVOnTqxaNGiYuvCwsLK9D5E9ecbGETLa/sRGBru6VCEqFEkmSmFwdf3kvZXTCYUU/GP91LrLU2rVq30zqIdO3YkKSkJk8lEw4YNq+R4F7J3717OnDnDK6+8QnR0NABbt2694D6tWrVi/fr1bmUbN26kWbNmpbbKlEVeXh7jxo3j1ltvpUWLFkyaNIld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8803LFy4kO3bt9OkSRMGDx7MuXPn3LZ7/vnneffdd9m4cSPHjx9n9OjRvPnmmyxevJgffviBlStX8s477+jbP/PMM8yfP5+5c+eye/duHnnkEW6//XbWrl3rVu/TTz/Na6+9xtatWzGZTHqMt956K4899hixsbEkJiaSmJjIrbfeWq7PoLwxlyQjI4OvvvqK22+/nUGDBpGVlaUnTeAaQuDGG2+kefPmbNu2jeeff55p06bp6w0GA2PGjGHRokVu9S5evJju3bvTqFEjNE1j6NChJCUlsWLFCrZt20bHjh0ZMGCA/nP44YcfGDlyJEOHDmXHjh16EgiuRLVevXq8+OKL+mdVkm+//ZapU6fy2GOP8ffffzN58mQmTJjA6tWr3bZ74YUXGD16NH/99Rc33HAD48aNK/b7IC6fgj4zcplJiArSPGj16tUaUOxx1113aQkJCSWuA7TVq1eX+RhWq1UDNKvVWmxdTk6OtmfPHi0nJ6fYuncnryr348DWZH3/A1uTtXcnr9KWztnmVu9Hj60rtl953XXXXdrw4cO106dPaxaLRUtISNCOHDmieXt7a6dPn9aGDx+u3XXXXZqmaVpmZqZmNpu1RYsW6fvn5eVpderU0WbPnq1pWuHP4ddff9W3mTlzpgZohw4d0ssmT56sDR48WK/X29tb27hxo1tskyZN0m677bZS6/3hhx80QP/Mn3vuOa1du3ZudRT87Hfs2KGXpaamuv3sKxJzaT788EOtffv2+vLUqVO1cePG6csffPCBFhISomVlZellc+fOdYtx+/btmqIo2pEjRzRN0zSn06nVrVtXe++99zRN07RVq1ZpgYGBWm5urtuxGzdurH3wwQeapmla9+7d3Y57vgYNGmhvvPGGW9n8+fO1oKAgfblHjx7aPffc47bNLbfcot1www36MqA988wz+nJmZqamKIr2448/lnjcC/2diEvndDi012/7hzZn9FAt/cxpT4cjRLVxofP3+Tx6a3bfvn2LNecXdaF1AkJDQxk6dCgLFy7Uv/mHhoa6bXPo0CHsdjs9e/bUy8xmM127diU+Pt5t27Zt2+qvIyIi8PX1pVGjRm5lmzdvBmDPnj3k5uYyaNAgtzry8vKKXeYqWm9UlOubZ0pKSqX07SlPzKWZN28et99+u758++2307t3b9LS0ggODiY+Pp527drhW+SW2e7d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O1tfPnr0KG+++SY///xzpQUmhBBXAukvI0TVq1AyM3z4cD755BMA0tLS6NatG6+99hojRoxg7ty5lRqgEELUZAUtM8ER0l9GiKpSoWRm+/btXHvttQB8/fXXREREcPToUT755BPefvvtSg1QCCFqsoKWGen8K0TVqVAyk52dTUBAAAC//PILI0eOxGAwcM0113D06NFKDVAIIWqytBTXGDPBMsGkEFWmQslMkyZNWLZsGcePH+fnn3/muuuuAyAlJYXAwMBKDVAIIWoyaZkRoupVKJl59tlnmTZtGg0bNqRbt250794dcLXSdOjQoVIDFEKImspuyyUrLRWQDsBCVKUKTWdw880306tXLxITE2nXrp1ePmDAAG666aZKC04IIWoya37nX28/f7z9/T0cjRBXLkUrOifBFSg9PZ2goCCsVqtcAhNCXFZOhwNrSjI56Vbqtmjl6XCEqFHKc/4u82Wm++67j+PHj5dp2y+++IJFixaVtWohhLgiGU0mQurUlURGiCpW5stMYWFhtG7dmh49evCPf/yDzp07U6dOHby9vUlNTWXPnj2sX7+ezz//nLp16/Lhhx9WZdxCCCGEEEA5LzOlpKQwb948Pv/8c/7++2+3dQEBAQwcOJB7771Xv7upOpDLTEIIT9n6/bfYbbm06NmHWpF1PB2OEDVKec7fFe4zk5aWxtGjR8nJySE0NJTGjRujKEqFAq5KkswIITzl44cnk5p4kltm/Jv6rdtdfAchhK485+8K3c0EEBwcTHBwcEV3F0KIK16b/tdx9uRxQurU83QoQlzRKpzMCCGEuLAu/xjl6RCEuCpUaNA8IYQQQojqolonMw6Hg2eeeYaYmBh8fHxo1KgRL774Iqqqejo0IYS4oIyzZzh78jiOvDxPhyLEFa9aX2aaNWsW77//PgsXLiQ2NpatW7cyYcIEgoKCmDp1qqfDE0KIUsX98gObl31Fu0E3MPDuKZ4OR4grWoWTGYfDwZo1azh06BBjx44lICCAU6dOERgYiH8lDdv9xx9/MHz4cIYOHQpAw4YNWbJkCVu3bq2U+oUQoqoUTGUQHCETTApR1Sp0meno0aO0adOG4cOH88ADD3D69GkAZs+ezbRp0yotuF69erFq1Sr2798PwM6dO1m/fj033HBDqfvYbDbS09PdHkIIcbml5SczQZEywaQQVa1CyczUqVPp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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# from ipywidgets import *\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib.backend_bases import MouseButton\n", - "%matplotlib inline \n", - "\n", - "# get_ipython().run_line_magic('matplotlib', 'inline')\n", - "def on_move(event):\n", - " if event.inaxes:\n", - " print(f'data coords {event.xdata} {event.ydata},',\n", - " f'pixel coords {event.x} {event.y}')\n", - "\n", - "\n", - "def on_click(event):\n", - " if event.button is MouseButton.LEFT:\n", - " print('disconnecting callback')\n", - " plt.disconnect(binding_id)\n", - "\n", - "\n", - "\n", - "sh_hash = [substring[:7] for substring in df[\"git_hash\"]]\n", - "for col in [\"PdV\", \"Cell Advection\", \"MPI Halo Exchange\", \"Self Halo Exchange\", \"Momentum Advection\", \"Total\"]:\n", - " plt.plot(sh_hash, df[col], label=col, linestyle='dashdot')\n", - "plt.xticks(rotation=90)\n", - "plt.xlabel(\"Commit (new --> old)\")\n", - "plt.ylabel(\"Time (s)\")\n", - "plt.legend()\n", - "\n", - "binding_id = plt.connect('motion_notify_event', on_move)\n", - "plt.connect('button_press_event', on_click)\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'https://github.com/UK-MAC/CloverLeaf_ref/compare/439c8d8..e37e1d7'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def create_git_comapre_link(df, row1, row2):\n", - " return \"https://github.com/\" + df.iloc[row1][\"git_repo_name\"] + \"/compare/\" + df.iloc[row1][\"git_hash\"][:7] + \"..\" + df.iloc[row2][\"git_hash\"][:7]\n", - "\n", - "# https://github.com/UK-MAC/CloverLeaf_ref/compare/e37e1d7..439c8d8 (old .. new)\n", - "create_git_comapre_link(df, 7,6)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "version False\n", - "Task_Count False\n", - "Thread_Count False\n", - "x_cells True\n", - "y_cells True\n", - "xmin False\n", - "ymin False\n", - "xmax False\n", - "ymax False\n", - "initial_timestep False\n", - "timestep_rise False\n", - "max_timestep False\n", - "end_step False\n", - "timestep False\n", - "ideal_gas False\n", - "viscosity False\n", - "pdv True\n", - "revert False\n", - "acceleration False\n", - "fluxes False\n", - "cell_advection True\n", - "momentum_advection True\n", - "reset False\n", - "summary False\n", - "visit False\n", - "tile_halo_exchange False\n", - "self_halo_exchange False\n", - "mpi_halo_exchange False\n", - "total True\n", - "the_rest False\n", - "end_time False\n", - "dtype: bool" - ] - }, - "execution_count": 67, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# import sqlite3\n", - "# import pandas as pd\n", - "import numpy as np\n", - "# cnx = sqlite3.connect('clover_perf_runner_test_run.db')\n", - "# str_query = \"SELECT * FROM TABLENAME WHERE total != 'None'\"\n", - "# df = pd.read_sql_query(str_query, cnx)\n", - "\n", - "from dsi.dsi import DSI\n", - "store = DSI('clover_perf_runner_test_run.db')\n", - "df = store.query(\"SELECT * FROM TABLENAME WHERE total != 'None'\", True)\n", - "\n", - "for column in df.columns:\n", - " if column == 'testname' or column.startswith(\"git_\"):\n", - " continue\n", - " df[column] = pd.to_numeric(df[column].fillna(value=np.nan), errors='coerce')\n", - "# df.describe(include=[np.number])\n", - "# df.select_dtypes(include='number').columns.to_list()\n", - "df.select_dtypes(include='number').std() > 0.5\n", - "# df[df.total>10]\n", - "# another = df.select_dtypes(include='number').std() > 1\n", - "# df[another[another].index]\n", - "# list(another[another].index)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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Wvqvo8qUvX8yhmHw1FNPMW3aNPbv389bb73FrFmz+O677ygpKWHq1Km0b9+eOXPm8Oc//5muXbuyevVqDMPgnXfeoXPnzsyZM4e7776bZcuWnXBf69evZ/DgwezYsYMOHTrw8ccfExAQwKxZs3jwwQcpKirC6XRy1113cdNNNx1VPisri9tuu420tDRM02TChAnceuutJ9znc889x9SpU3G5XMTFxfHmm28SHR1Nr169eOihhxg5ciQLFy7k2muvZenSpURHR/Pss8/ywQcfYLFYCAoK4qeffgLA5XJxxx13MH/+fFwuF++99x7du3fH5XIxdOhQsrOzKSwsJDU1lX//+98EBwczefJk/vvf/xIZGcnatWtxOBxMnTqVpKQkAB544AE++eQToqKiOO+885g9e7bvPE6ZMoVXX30Vp9NJWFgYr732Gh06dKjSz1VEpCbrNvQyXCXF/g5DRMQv1HJTzcLDw1myZAnPPvssl156KX379mXFihXccMMNPPnkk77tVq9ezQ033MDy5cu59957ufbaa09pPytXruTrr7/mt99+Iysri88//xyArl27Mm/ePFasWMHcuXN59NFH2bNnz1HlJ0yYQJs2bVizZg0//fQTjz/+OEuWLDnu/j766CM2bdrEwoUL+fXXXxk9ejR//OMfCQoK4tNPP+Wvf/0rS5cu5brrrmPKlClER0fz3nvvMX36dObPn8+qVav43//+h8PhAGDdunWMGzeOVatW8ac//YkHHngAAKvVykcffcSyZctYu3Yt4eHhvP766744Fi9ezDPPPMOaNWsYPHgwzz77LABff/01M2bMYNWqVSxcuJAtW7b4ysyfP5+PP/6YuXPn8uuvv/LEE0+ctOufiEht0f78QXS+4BJ/hyEi4hd1tuWmqq0rFZW14JTpE9+HPvF9TiuOq6++GvAmGRaLhaFDhwLQrVs3vvjiC992ycnJ9O/fH4BRo0Zx6623snv37irv54orriAoKAiAHj16+C7ms7OzGT9+PJs2bcJms7F//37WrVtH48aNK5WfOXMmq1atAqBRo0ZcccUVzJo1ix49ehxzf9OnT2fZsmV069YNALfbjdVqBSAlJYVnn32W3r178/jjj9O3b18AZsyYwe233054eDgAERERvvpat27tG3fTu3dvnnvuOQBM0+SFF17gm2++weVycfDgQc477zxf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DoeDffv2+epbvHgxq1atonnz5tx///08++yzvPnmmzRs2JCvv/6a0NBQ3G43l156KZ9//rkvUVm8eDFLly4lMjKSa665hjfffJO//e1vvPnmm+zYsYP169fjcrno37+/b4a9jz76iE2bNrFw4UKsVitTpkzhj3/8I19++eUp/GRFRGoWj8tJSEQkQeEN/B2KiIjf1dnkpuXvGIsS+7e/EVthObRfX0L7nXo9AJ07d2bDhg3ccccdnH/++VxyySV8+eWXrFq1qtI4mn379lFSUvK79gFwzTXXYLVaCQoKIjU1lS1bttC7d2+2bt3KmDFjyMjIwGazsX//frZv315pzFNeXh4rV65k/PjxALRq1Yq+ffsyb948Ro8efcz9TZ8+nUOHDvHZZ58B3paSli29A1j79evH+PHj6d27N1OmTKFVK++sPTNmzOD555/H4XAAVOpq17dvX5o3bw5A7969eeWVVwDweDzcd999zJs3D9M02bt3L6mpqb7k5uKLLyYyMtJXbs2aNQDMnj2bsWPHYrPZsNlsjB49ml9++cUX+7Jly+jWrRsAbrcbq9X6u8+9iEhNkJjajcTUbv4OQ0SkRqizyY2/JSUlsX79en766SdmzpzJvffey4UXXsi4ceN47LHHzth+AgMDfa+tVisulwvwJj3PPfccl112GQCRkZEUFRVVKlvWzevIvton6rttmiavv/46AwcOPOb7K1asICYmhh07dpxW/JMmTSI7O5vFixcTGBjIxIkTK8V/vHIn6ntumiYPPvgg48aNq1JsIiIiIlK71NkxN/6WkZGBYRiMGDGC5557DtM0GTt2LO+//75vnIfH42HZsmXVsv+cnBwSExMB+OCDD8jJyTlqm/DwcFJTU3nvvfcAbxe3+fPn06dPn+PWO2LECCZNmkRBQQEABQUFrFu3DoBXX32VnJwcVq1axRtvvOG7SeuIESN48cUXKS4uBqjULe1E8cfFxREYGEhWVhaffvpplY57wIABfPDBB7hcLoqKivjkk08qxf7666/7xuc4nU5WrFhRpXpFRGqiwvw8vnzuSdKWLfZ3KCIiNYJabqrJmjVruP/++zFNE4/Hw9ixYznvvPN46qmnuPTSS3G73TidToYOHUr37t3P+P5feuklLr/8cuLj4+nduzfNmjU75nYffvghf/jDH3jxxRcxDIO33377uONtAO6/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q6Os3JHLLpcLj8eDYRgsXboUu91+wliO3P+RbDYbbrfbt1xUVHTCeo5cLiwsPGa9APPmzePVV19lwYIFxMTE8NVXX/HYY48B+JKH48U+cuRIPvjgAyIiIhg8eDBRUVG+ctOnTycpKalSmZN1jzueI38eR/5sqnoeRUSqiz3AQYNGcf4OQ0SkxtGYmxrgggsu4OOPPyYvLw+At956i8GDB59SHWFhYfTr149nnnnGt2737t1kZGSctGx4eLhvjA9Ay5YtWbVqFUVFRbhcLj766KNTiuVEcnJyCA8PJzIykpKSEt58803fe4MGDWLq1KkcPnwYt9vN5MmTK5UdN24ckydP5t133+Wmm27yrR8xYgTPPPOML8nIyckhLS2N0NBQ+vbtywsvvODbdt++fcc85ooGDRrEt99+S2ZmJgBvvPEGgwYN0sBdEakRMn5by/bVK3HrixURkaMouakBLr74YsaOHUvv3r3p2LEjhw4d4sknnzzlej788EN+++03OnbsSMeOHbnyyivJzs4+ablbb72Vxx57zDehQO/evbnooovo0KEDQ4YMoWXLlr/nsI7p4osvJjk5mTZt2nDRRRf5urcBDBs2jGHDhtG5c2cGDhxIp06dKpUtm9ls69atXHjhhb71L774IjabjdTUVDp16sTgwYPZtm0b4J0YYNGiRbRv357OnTvz6quvAjB27Fg++ugj34QCFbVv356nn36aCy+8kE6dOvHLL79USsJERPxpyfRPmfbsI8DxW7tFROorwzxRX6AaKCEh4ajWCLfbzaZNm0hJScFqtfopMpFy+kyKSHUwTZM3/jCW8OgYxjz1wskLiIicBce6PvcXtdyIiIjUEnnZ+yk4mEtsUit/hyIiUiNpQgGpFo899phvNriKPv/88zPazU1EpD7JSt8MQGzLZD9HIiJSMym5kWrx8MMPV7qJqIiInL6s9DQA4tRyIyJyTOqWJiIiUktkbtmMzR5AVEIzf4ciIlIjKbkRERGpBUzTJCs9jZjEFlg0UYmIyDEpuREREakFDu3Loig/T5MJiIicgJKbavTII49QUlJy0u1efPFF9u7dW6U6+/fvz4wZM043NBERqWV8421aKrkRETkeJTfV6NFHHz3jyY2IiNRPmaXJTWySZkoTETkeJTfV5LbbbgPg3HPPJTU1laysLC6//HI6duxIhw4deOuttwDv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MddPnTqVrVu3AnDOOecQGxvLvHnz6N+/f3WHJCIiUmMUFxSQszuD1r37+TsUEZFazy9jbrKzs/F4PMTExPjWJSYmsmPHDn+EIyIi4jd7t5aOt2nZys+RiIjUfn6bUMAwjErLpmkec7tJkyaRkJDge+Tn55+N8ERERM6KzHRvchOnyQRERE6bX5KbqKgoAPbt2+dbt337dpo1a3bUthMnTiQjI8P3CFV/ZBERqUOySpObRppMQETktPmt5eaqq67itddeA2Dp0qVkZmbSt29ff4UjIiLiF1npm4lokoAjONjfoYiI1HrVntzceeedJCQkkJGRweDBg0lO9n4z9eyzz7JgwQJatWrFjTfeyJQpU7DZqn1+AxERkRqjuKCA3KxMdUkTETlDDPN4g11qqLJESUREpC4ozM/DWVREeHTMyTcWEamBatL1uZpKRERE/CgoNIyg0DB/hyEiUif4bcyNiIhIfbd15XL2pG30dxgiInWGkhsRERE/mfXO63z/fy/5OwwRkTpD3dJERET8wDRNBo2/A7fT6e9QRETqDCU3IiIifmAYBi1Su/k7DBGROkXd0kRERPyg4GAubpdabUREziQlNyIiIn4w8+3XefWma3CpW5qIyBmj5EZERMQPMtM30zCuMTa73d+hiIjUGUpuREREzrKCg7nk7d9HbFKyv0MREalTlNyIiIicZVnpaQBKbkREzjAlNyIiImdZWXITl9TKz5GIiNQtSm5ERETOssz0NCxWK9HNE/0diohInaLkRkRE5CzLSt9MVNPm2AMc/g5FRKROUXIjIiJyFuXnHCD/QDZxGm8jInLGKbkRERE5i8onE9B4GxGRM03JjYiIyFmUlb4Z0ExpIiLVwebvAEREROqTlt16YrFYiW6W6O9QRETqnConN4WFhezZs4egoCAaN25cnTGJiIjUWbFJyWq1ERGpJifslubxeHjvvffo27cv0dHR9OvXjw4dOhAXF8ftt9/Opk2bzlacIiIitZ7b5cTldPo7DBGROuuEyc25557LihUreP755zl48CC7du0iOzub1atX07t3b26++WY+/vjjsxWriIhIrbZ1xXJeueEqNi2e7+9QRETqpBN2S/vyyy+JjY09an2jRo24/vrruf7669m7d2+1BSciIlKXBAQFk9i5C5GN4/0diohInWSYpmmeSoGDBw+yc+dOOnToUF0x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yc2KFSvOVhwiIiK1mrOkmOyd24lNauXvUERE6i3NliYiInIG7N++DY/bTZy6pImI+I2SGxERkTMgs3QygdiWarkREfEXJTciIiJnQJYmExAR8TslNyIiImdAVvpmwqJjCA5v4O9QRETqLSU3IiIip8lZVER2xk7iNJmAiIhfnfA+NyIiInJyhsXCsL/cS2BouL9DERGp15TciIiInCZbQAApvfr6OwwRkXpP3dJERERO0+HcHEyPx99hiIjUe0puRERETtOnjz/A+/dN8HcYIiL1nrqliYiInAbTNGnRpTt2R6C/QxERqfeU3IiIiJwGwzA4/7px/g5DRERQtzQREREREakjlNyIiIichpnv/B+fPfkQbpfL36GIiNR76pYmIiJyGnasWYlperDa9F+qiIi/+bXlpri4mD/+8Y+0atWK9u3bc9111/kzHBERkVNSXHCYnD27iE1q5e9QREQEP7fc3H///VgsFjZt2oRhGOzZs8ef4YiIiJySrPQtAMQlJfs5EhERAT8mN4cPH+bdd98lIyMDwzAAaNy4sb/CEREROWVZW9MAiFVyIyJSI/itW9qWLVuIioriiSeeoHv37vTr149Zs2Ydtd2kSZNISEjwPfLz8/0QrYiIyNGytmwGoFELJTciIjWB35Ibp9NJeno67dq1Y9myZbz66qtcc8017Nu3r9J2EydOJCMjw/cIDQ31U8QiIiKVZaWnEdEkAUdwsL9DERER/JjcNG/eHIvFwpgxYwDo3LkzLVq0YN26df4KSUREpMqK8vPJzdqj8TYiIjWI35Kb6OhoBg0axPfffw/A9u3b2bp1K61bt/ZXSCIiIlWWlV423kYzpYmI1BR+nS3tjTfeYNy4cdx3331YrVbeeustTSogIiK1gm8ygZZquRERqSn8mtwkJSUxZ84cf4YgIiLyu4RGRNK0XUcaJSb5OxQRESllmKZp+juIU5GQkEBGRoa/wxAREREREWrW9bnfxtyIiIiIiIicSUpuRERETtHOdav59PEH2LXxN3+HIiIiFSi5EREROUUH9+1l9+YNGIa/IxERkYr8OqGAiIhIbdSh/2DanTfA32GIiMgRlNyIiIj8DhaL1d8hiIjIEdQtTURE5BQUHDrIvI+n+G7iKSIiNYeSGxERkVOQuWUTi6d9wp60Tf4ORUREjqDkRkRE5BRkbfG22MQlJfs5EhER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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "from matplotlib.pyplot import figure\n", - "%matplotlib inline \n", - "\n", - "figure(figsize=(12, 6), dpi=80)\n", - "\n", - "sh_hash = [substring[:7] for substring in df[\"git_hash\"]]\n", - "commit_dates = pd.to_datetime(df[\"git_committed_date\"]).dt.strftime(\"%b %d, %Y\")\n", - "for col in [\"pdv\", \"cell_advection\", \"mpi_halo_exchange\", \"self_halo_exchange\", \"momentum_advection\", \"total\"]:\n", - " plt.plot(sh_hash, pd.to_numeric(df[col]), label=col, linestyle='dashdot')\n", - "plt.xticks(sh_hash, commit_dates, rotation=-25, ha=\"left\", rotation_mode=\"anchor\")\n", - "# plt.xticklabels(commit_dates)\n", - "plt.xlabel(\"Commit dates\")\n", - "plt.ylabel(\"Time (s)\")\n", - "plt.legend()\n", - "\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "https://github.com/UK-MAC/CloverLeaf_ref/compare/439c8d8..e37e1d7\n" - ] - } - ], - "source": [ - "comp_url = create_git_comapre_link(df, 7,6)\n", - "# comp_url = \"https://www.google.com\"\n", - "print(comp_url)\n", - "# IFrame(\"https://github.com/UK-MAC/CloverLeaf_ref/compare/439c8d8..e37e1d7\", 900,500)" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2021-08-03\n", - "August 03, 2021\n", - "0 August 09, 2021\n", - "1 August 03, 2021\n", - "2 August 03, 2021\n", - "3 August 20, 2020\n", - "4 July 14, 2020\n", - "5 July 14, 2020\n", - "6 July 14, 2020\n", - "7 July 13, 2020\n", - "8 July 13, 2020\n", - "9 July 03, 2020\n", - "10 July 03, 2020\n", - "11 July 01, 2020\n", - "Name: git_committed_date, dtype: object\n" - ] - } - ], - "source": [ - "from datetime import datetime\n", - "\n", - "print(df[\"git_committed_date\"][1])\n", - "datetime_object = datetime.strptime(df[\"git_committed_date\"][1], '%Y-%m-%d').strftime(\"%B %d, %Y\")\n", - "print(datetime_object)\n", - "pd.to_datetime(df[\"git_committed_date\"])\n", - "# print(df[\"git_committed_date\"].strftime(\"%B %d, %Y\"))\n", - "\n", - "commit_dates = pd.to_datetime(df[\"git_committed_date\"]).dt.strftime(\"%B %d, %Y\")\n", - "print(commit_dates)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'kernels/update_halo_kernel_c.c': [60,\n", - " 60,\n", - " 60,\n", - " 60,\n", - " 59,\n", - " 59,\n", - " 59,\n", - " 59,\n", - " 59,\n", - " 59,\n", - " 59,\n", - " 59],\n", - " 'kernels/viscosity_kernel_c.c': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " 'kernels/revert_kernel_c.c': [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n", - " 'kernels/accelerate_kernel_c.c': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n", - " 'kernels/advec_cell_kernel_c.c': [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],\n", - " 'kernels/pack_kernel_c.c': [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],\n", - " 'kernels/generate_chunk_kernel_c.c': [4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5],\n", - " 'kernels/flux_calc_kernel_c.c': [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n", - " 'kernels/PdV_kernel_c.c': [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n", - " 'kernels/reset_field_kernel_c.c': [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],\n", - " 'kernels/advec_mom_kernel_c.c': [12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12,\n", - " 12],\n", - " 'kernels/initialise_chunk_kernel_c.c': [3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2],\n", - " 'kernels/field_summary_kernel_c.c': [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],\n", - " 'kernels/calc_dt_kernel_c.c': [0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2],\n", - " 'kernels/ideal_gas_kernel_c.c': [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]}" - ] - }, - "execution_count": 82, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pickle\n", - "def get_var_occurance(tracked_var, tracked_file):\n", - " f_list = list()\n", - " for gh in df[\"git_hash\"]:\n", - " with open(gh + '.pickle', 'rb') as handle:\n", - " b = pickle.load(handle)\n", - " val = 0\n", - " if tracked_var in b:\n", - " if tracked_file in b[tracked_var]:\n", - " val = len(b[tracked_var][tracked_file])\n", - " f_list.append(val)\n", - " # print(b[tracked_var]['kernels/field_summary_kernel_c.c'])\n", - " # print(f_list)\n", - " return f_list\n", - "\n", - "def get_var_occurance_accross_files(tracked_var):\n", - " f_list = dict()\n", - " for gh in df[\"git_hash\"]:\n", - " with open(gh + '.pickle', 'rb') as handle:\n", - " b = pickle.load(handle)\n", - " if tracked_var in b:\n", - " for tracked_file in b[tracked_var]:\n", - " if tracked_file not in f_list:\n", - " f_list[tracked_file] = get_var_occurance(tracked_var, tracked_file)\n", - " # print(f_list)\n", - " return f_list\n", - "get_var_occurance_accross_files(\"#pragma omp simd\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "from matplotlib.pyplot import figure\n", - "%matplotlib inline \n", - "plt.style.use('dark_background')\n", - "\n", - "tracked_var = \"#pragma omp simd\"\n", - "tracked_file = 'kernels/field_summary_kernel_c.c'\n", - "f_list = get_var_occurance(tracked_var, tracked_file)\n", - "figure(figsize=(12, 6), dpi=80)\n", - "\n", - "sh_hash = [substring[:7] for substring in df[\"git_hash\"]]\n", - "commit_dates = pd.to_datetime(df[\"git_committed_date\"]).dt.strftime(\"%b %d, %Y\")\n", - "for col in [\"pdv\", \"cell_advection\", \"mpi_halo_exchange\", \"self_halo_exchange\", \"momentum_advection\", \"total\"]:\n", - " plt.plot(sh_hash, pd.to_numeric(df[col]), marker='^', label=col, linestyle='dashdot')\n", - "# plt.scatter(sh_hash, f_list, s=100, c='#000000', marker='.', zorder=2)\n", - "plt.xticks(sh_hash, commit_dates, rotation=-25, ha=\"left\", rotation_mode=\"anchor\")\n", - "# plt.xticklabels(commit_dates)\n", - "plt.xlabel(\"Commit dates\")\n", - "plt.ylabel(\"Time (s)\")\n", - "plt.legend()\n", - "plt.rcParams.update({'font.size': 14})\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA6oAAAHOCAYAAACRhvR7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAAxOAAAMTgF/d4wjAACFpUlEQVR4nO3dd1yVBfsG8IshICIqLlyAKyeiZebMjTM1tdIcaEN7zTRNMzUVHC0tR1mO3KamYq7cO2cupiACHkARZKkg+3D//jDOz+MBPCjwnAPX9/O5P+/Lc57znPu5JOXmWSYABEREREREREQGwlTpBoiIiIiIiIiexkGViIiIiIiIDAoHVSIiIiIiIjIoHFSJiIiIiIjIoHBQJSIiIiIiIoPCQZWIiIiIiIgMCgdVIiIiIiIiMigcVImIiIxc06ZNcfjwYcTGxkJEsG7dOqVb0uLo6AgRwZw5c4r8sw0xDyIiej4OqkREBszNzQ0igho1agAAWrZsCRFBly5dFO6MDIWZmRl27dqFVq1a4Y8//oC7uzt2796tdFtEREQvxVzpBoiIKHdvvvkmgoODcffuXQBAp06dkJ6ejgsXLijcGRmKOnXqoH79+vjqq6/w/fffK91Oju7evYuGDRsiNjZW6VaIiMhIcFAlIjJgb775Jk6fPq35ulOnTrh8+TJSUlIU7IoMib29PQAgKipK4U5yl5mZiZs3byrdBhERGRGe+ktEZEAqVKiAunXrom7dunj99ddRr149BAcHo27duqhXrx7at2+PGzduaNaxsrLSvHfdunUQEZQuXRpLlizB3bt3kZycjEuXLsHV1VXns06ePInbt2+jQoUK+O2333D37l1kZmaiY8eOAIBevXrhjz/+QHBwMJKTkxEfH48DBw6gTZs2OfZub2+PTZs2IS4uDo8ePcLRo0fRvHlzTV9PmzNnDkQEderUwYwZMxAaGorHjx/j4sWLmu3XrFkTW7duRWxsLB49eoSNGzeibNmyWtspXbo0vvjiC5w6dQr37t1Damoqbt++jaVLl6J8+fL5yt7V1RUnTpzAw4cPkZSUhAsXLuC9997TWa+geu/YsSNEBG5ubnj33Xdx/fp1JCcnIywsDHPnzkWpUqWe2/Pt27dx5swZAMD69eshIhARODo6atZ5//33cfHiRSQlJeHhw4c4fvw4unXrprOt7D8nKysrfPvtt7h9+zYyMjLg5uaWZw+NGjXC1q1bERYWhtTUVNy9excnTpzA0KFDNevkdo1q9vWj2dfYPnr0CNHR0fjxxx9hZmYGAPjggw/g5+eHlJQUBAQEoH///jn20b17d1y8eBHJycmIjIzEkiVLULp06edmSEREhktYLBaLZRg1Z84cyY+OHTtq3rtu3ToREfn7778lJCREFi5cKCtWrJAHDx5IRkaG9OnTR+uzTp48KZGRkXL9+nXx9/eXZcuWyfLly6VFixYCQC5cuCB+fn6yfv16+fbbb2Xt2rWSkJAgaWlp0q5dO61t2draSlBQkIiI7N+/X7755hvZu3evPHz4UE6fPi3yZFLV2c+9e/dKRESE/Prrr7Jq1Sp5/PixPHz4UJo2bSphYWFy5swZWbRokRw9elRERDZs2KC1nQYNGkh6erocPnxYfvnlF/nhhx/k0KFDIiJy7do1sbCw0Cv3kSNHilqtlpiYGPnll1/kp59+kvDwcBERmT59eqH03rFjR82fV0pKiqxfv16+++478fb2FhGRbdu2PbfviRMnav7c//rrL5kzZ47MmTNHypUrJwBk1qxZIiISFhYmP/30k/zyyy8SExMjarVahg0bprWt7O0cOHBAbt++LStWrJDFixdLz549c/38GjVqyMOHDyUxMVE2bdokCxYskFWrVsnly5dl165dmvUcHR1FRGTOnDla7xcROX36tCQkJMiBAwdk4cKFcunSJRERWbRokUyZMkXi4uJk7dq18vPPP0tMTIykp6fLK6+8orWdXr16SUZGhjx8+FBWrFghCxculJCQEPn7779FRGTdunWK/7fNYrFYrHyX4g2wWCwW679q1KiRDBo0SAYNGqQZJLO/3rRpk2RmZsp7772nWVapUiXNe7MHDV9fXylTpoxmecOGDSU5OVnCw8PFzMxMs/zkyZOagatUqVI6vTg5Oeksq1KlikRERMiJEye0ln/zzTciIjJjxgyt5V9++aVmqH56efawd+PGDbGzs9MsHzhwoIiIJCQkyPfff6/1nj179kh6erpUrVpVs8za2loqV66s0+ewYcNERGTkyJHPzbx8+fLy6NEjuX//vtSsWVNr+a1btyQjI0Pq1atX4L1nD6oiIp06ddIsNzMz0wy3rq6uz+0/eztubm5ay1955RXJzMyUwMBAsbW11Sx3cHCQuLg4efjwodby7O+f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HTogJSUFV69eBfD/pzTmdFfTnAwePBj79+/HsGHDMGrUKAQFBaFPnz7Yv3+/3j0kJiaiS5cuOHToELp3744xY8YgIyMDb775Ji5fvqyz/sOHD/Hmm29iy5YtaNeuHT777DNYW1ujY8eOePjwoeZ6yYK2a9cuDB8+HPHx8RgzZgzeeust7N27F66urvm6NnLt2rXo06cPbty4ATc3N3zyySeIiorCsGHDMH/+/ELpPdv27dsxatQotGjRAp9//jns7Owwb948DB8+/KW37e7ujhEjRuD+/fv45JNP4ObmBj8/P/Tu3RsbN2586e1v27YNmzdvRu3atfHBBx9gzJgxKFOmDKZMmYKBAwe+9Pb1dfDgQfTu3Rs3b97EqFGjMGzYMPz9998YPHhwkfVAREQFywRPnlNDRERGbt26dRg1ahRMTEyUbkXDxMQEISEhiI2NRatWrZRux6B07NgRp06dwqhRo7Bhwwal2yEiIjIoPKJKREQFwt7eXmfZ5MmTUbt27RxvykRERESUG16jSkREBeLcuXMICwuDr68vsrKy8Prrr6Ndu3YIDg7GkiVLlG6PiIiIjAiPqBIRUYFYs2YN7OzsMHLkSIwbNw41a9bE8uXL0bZt20K7RpWIiIiKJ16jSkRERERERAaFR1SJiIiIiIjIoBjlNaqpqamIiYlRug0iIiIiIiJFVa5cGVZWVkq3UeCMclCNiYlBrVq1lG6DiIiIiIhIUREREUq3UCh46i8REREREREZFA6qREREREREZFA4qBIREREREZFB4aBKREREREREBoWDKhERERERERkUDqpERERERERkUDioEhERERERkUHhoEpEREREREQGRZFBtV+/frhw4QIePnyI+/fv4++//0aLFi2UaIWIiIiIiIgMTJEPqt27d8dff/2FkJAQDB48GB9//DEqVqyIEydOoFatWkXdDhERERERERkY86L+wKFDhyIsLAwjRoyAiAAALl++jLt376J3795YuXJlUbdEREREREREBqTIj6iWKlUKiYmJmiEVAB4+fIisrCyYmJgUdTtERERERERkYIr8iOqaNWtw5MgRfP7551i/fj2sra2xYMECREVFYfv27UXdToEwMTVFz08/Qt3XX8Xt6764sOMvSFaW0m0pzsTUFG3eeRu1Wzgzl/8wE13MJGfMRRcz0cVMcsZcdGVnUrNxAwRfuooTazeV+EyIyLCZAJDnrlXA3nrrLWzevBm2trYAAJVKhV69eiEwMFCv90dERBjU9axdP3JDz/Efw9TMTOlWiIiIiPKUkZaGI7+txYk1G5VuhYgKgKHNRgWlyI+otm3bFhs3bsTmzZvh6ekJa2trTJo0CYcOHUK7du1w9+5dnfdMmjQJkydP1nxtY2NTlC0/V703XtMaUuMj78H70HEFOzIMLj27wq56Nc3XzIWZ5ISZ5Iy56GImuphJzpiLrqczKWVpifqtW3JQJSKDVuSD6tKlS3H27Fl8+umnmmXHjx+HSqXClClTMGnSJJ33LF68GIsXL9Z8HRERUSS96iv40lXUbtEMpSwtkZGWhgvbd/MvfwApiUno/slo5vIUZqKLmeSMuehiJrqYSc6Yi66UxCTN2V8Zaem4dfGK0i0REeWpyAfVxo0b48CBA1rLUlJScPPmTdSvX7+o2ykQJ9ZugoigfuuWuHXxCk6u26x0SwaBuehiJrqYSc6Yiy5moouZ5Iy56DqxdhNqNW0I566d4H3kODMhIoNX5Neo+vv7Q6VSoU+fPpplZcqUgUqlwrZt2/DZZ589dxvF9TxsIiIiosJS7ZW6mOK5GSfWbMTfS35Tuh0iKiDFdTYq8sfT/PLLL5rnpXbv3h0DBgzAwYMHUbZsWT5DlYiIiKiQRAXfRmrSYzg2d1a6FSKi5yryU39/++03pKamYvz48RgyZAhSU1Ph7e2NLl26wM/Pr6jbISIiIioRJCsL4b7+qN3CBabmZsjKVCvdEhFRrop8UAWAdevWYd26dUp8NBEREVGJpfLyxSttWqFGg1cQ4R+gdDtERLkq8lN/iYiIiEgZKu8nZ685ujRVuBMiorxxUCUiIiIqIcJ8ngyqTrxOlYgMHAdVIiIiohIiNTEJUcGhPKJKRAaPgyoRERFRCaLy8oVd9WqwrVJZ6VaIiHLFQZWIiIioBAn77zpVJx5VJSIDxkGViIiIqARRefsC4HWqRGTYOKgSERERlSAxqnAkP3zE61SJyKBxUCUiIiIqQUQEKm9f1GzUAOYWFkq3Q0SUIw6qRERERCVMmLcfzC0sULNRA6VbISLKEQdVIiIiohIm+4ZKPP2XiAwVB1UiIiKiEibc9way1GreUImIDBYHVSIiIqISJi05GfduhXBQJSKDxUGViIiIqARSefnCtnIlVKhur3QrREQ6OKgSERERlUDZ16k6ufCoKhEZHg6qRERERCWQyssXAHj6LxEZJA6qRERERCVQ3J27SIyL551/icggcVAlIiIiKqHCvH1RvUF9WJS2UroVIiItHFSJiIiISiiVtx/MzM1Rs0kjpVshItLCQZWIiIiohNJcp8obKhGRgeGgSkRERFRCRfgHQp2RCSdep0pEBoaDKhEREVEJlZmWhruBQbyhEhEZHA6qRERERCWYytsXNnYVUMmhptKtEBFpcFAlIiIiKsHC/rtO1ZHXqRKRAeGgSkRERFSCqbz9AABOzTmoEpHh4KBKREREVII9iIrGg+j7vKESERkUDqpEREREJVyYtx/s69eFZRlrpVshIgKg0KB68uRJiEiONW3aNCVaIiIiIiqxVN6+MDU1hYNzE6VbISICAJgr8aHjxo2Dra2t1rIRI0bg008/xYEDB5RoiYiIiKjEUv13QyWn5s64dfGywt0QESk0qAYEBOgsW7ZsGXx8fODr66tAR0REREQl192AIGSmp/N5qkRkMAziGtV69eqhVatW2Lx5s9KtEBEREZU46owMRPgHwqlZU5iYmCjdDhGRYQyqw4cPh1qtxpYtW5RuhYiIiKhECvP2Q2nbsqhS21HpVoiIDGNQff/993H69GncvXs3x9cnTZqEiIgITdnY2BRxh0RERETFm8rLBwCfp0pEhkHxQfWNN95A/fr18zztd/HixahVq5amkpKSirBDIiIiouJP5e0HAHB04aBKRMpTfFAdPnw4UlJSsHPnTqVbISIiIiqxEmPjEHcnkkdUicggKDqompmZ4d1338W+ffuQmJioZCtEREREJV6Yjx+q1nFC6WceI0hEVNQUHVR79OiBKlWq8G6/RERERAYg+3mqji5NFO6EiEo6RQfV4cOHIzY2FgcPHlSyDSIiIiICEOb9ZFB14nWqRKQwxQbVMmXKoF+/fti+fTsyMzOVaoOIiIiI/hMZFIy05BQOqkSkOHOlPvjx48d8zAwRERGRAcnKVCPCPwC1nBvBxNQUkpWldEtEVEIpftdfIiIiIjIcKi9fWJUpA/t6dZRuhYhKMA6qRERERKQR9t/zVPmYGiJSEgdVIiIiItII8/lvUOV1qkSkIA6qRERERKTxOOEBYlThPKJKRIrioEpEREREWlTefqjkUBM2dhWUboWISigOqkRERESkRfXf81QdXZoq3AkRlVQcVImIiIhIS9h/g6oTB1UiUggHVSIiIiLSEhV8G6lJj+HI61SJSCEcVImIiIhIi2RlIdzXHw5NGsPU3EzpdoioBOKgSkREREQ6VF6+KGVliRoNXlG6FSIqgTioEhEREZEOlfeT56nyhkpEpAQOqkRERESkI8znyaDK56kSkRI4qBIRERGRjtTEJEQFh/KIKhEpgoMqEREREeVI5eULu+rVYFulstKtEFEJw0GViIiIiHIU9t91qnyeKhEVNQ6qRERERJQjlbcvAN5QiYiKHgdVIiIiIspRjCocyQ8f8YZKRFTkOKgSERERUY5EBCpvX9Rs1ADmFhZKt0NEJQgHVSIiIiLKVZi3H8wtLFCzUQOlWyGiEoSDKhERERHlKvuGSrxOlYiKEgdVIiIiIspVuO8NZKnVvE6ViIoUB1UiIiIiylVacjLu3QrhoEpERUrvQbVXr14YNmyY5uuaNWvi+PHjuHfvHjZs2ABra+tCaZCIiIiIlKXy8oVt5UqoUN1e6VaIqITQe1B1d3dHtWrVNF8vWbIEDRo0wB9//IEePXrAw8OjUBokIiIiImVlX6fq5MKjqkRUNPQeVOvXrw8fHx8AgLW1Nfr06YPJkydjypQpmD59OgYOHFhoTRIRERGRclRevgDA03+JqMjoPahaWFggLS0NANCmTRuYmZnh4MGDAIBbt25pHW3V19ChQ3H58mUkJycjNjYWR44cQcWKFfO9HSIiIiIqPHF37iIxLp53/iWiIqP3oBocHIw+ffoAAEaOHIl///0XiYmJAAB7e3s8ePAgXx/85ZdfYv369Thy5Ah69+4NNzc3eHt7w9LSMl/bISIiIqLCF+bti+oN6sOitJXSrRBRCSH61MiRI0WtVktMTIxkZmbKO++8o3lt+fLlcujQIb22A0Dq168v6enp8sknn+j9nqcrIiLihd7HYrFYLBaLxXqx6vzBcPnR94LUadlC8V5YLNb/V3Gdjcyhp40bNyI0NBStWrXC1atXcfr0ac1r9+/fx/79+/XdFEaPHo20tDSsWbNG7/cQERERkXI016m6OCP0ynWFuyGi4i5fz1E9e/YsfvrpJ60hFQA8PDw016vqo02bNggMDMSoUaMQFhaGjIwMXLt2Da6urvlph4iIiIiKSIR/INQZmXDidapEVATyNaiam5vj448/xurVq3H48GHUq1cPADBo0CC88sorem/H3t4eDRo0gLu7O2bOnIk+ffogOjoa+/bty9d2iIiIiKhoZKal4W5gEG+oRERFQu9B1dHREQEBAViyZAmcnZ3RtWtXlC1bFgDQtWtXfPnll3p/qJmZGcqWLYuPPvoImzdvxpEjR/D2228jPj4eU6ZM0Vl/0qRJiIiI0JSNjY3en0VEREREBUPl7Qsbuwqo5FBT6VaIqJjTe1BdsmQJEhMTUadOHbRr1w4mJiaa106dOoU333xT7w+Nj48HAJw8eVKzLDU1FRcvXkTjxo111l+8eDFq1aqlqaSkJL0/i4iIiIgKRth/16k6uvB5qkRUuPQeVLt27Qp3d3dER0dDRLReu3fvHmrUqKH3h/r7+yMrK0tnuYmJCayseMtzIiIiIkOk8vYDADg156BKRIVL70FVRHIcLgGgUqVKSE5O1vtD9+/fD1NTU3Tt2lWzrHTp0mjTpg2uXr2q93aIiIiIqOg8iIrGw+gY3lCJiAqd3oPqhQsXMGbMmBxfGz58OM6cOaP3h+7evRuXLl3C77//Djc3N/Tq1Qt79uxBmTJl8OOPP+q9HSIiIiIqWipvX9jXrwvLMtZKt0JExZjeg+qsWbPQrVs3XLhwAZ9//jlEBIMHD8bu3bvh6uqKOXPm6P2hIoI+ffrg0KFD+Omnn+Dp6YlSpUqhc+fOCAoKeqEdISIiIqLCp/L2hampKRycmyjdChEVc6Jvvf7663Ly5ElJT08XtVotGRkZcurUKXnttdf03kZBVERERJF+HovFYrFYLBbrSTk0ayI/+l6Q7p98oHgvLBar+M5G5siHy5cvo3PnzrC0tISdnR0ePHiAlJSU/GyCiIiIiIzY3YAgZKan83mqRFSo8jWoZktLS8O9e/cKuhciIiIiMnDqjAxE+AfCqVlTmJiY6DwNgoioIOh9jerq1auxffv2HF/btm0bVq5cWWBNEREREZHhCvP2Q2nbsqhS21HpVoiomNJ7UO3evTs8PT1zfG3Xrl1wdXUtsKaIiIiIyHCpvHwA8HmqRFR49B5Uq1atiujo6Bxfu3//Puzt7QusKSIiIiIyXCpvPwCAowsHVSIqHHoPqlFRUXBxccnxtRYtWiAmJqbAmiIiIiIiw5UYG4e4O5E8okpEhUbvQfWvv/7C7Nmz0bZtW63l7dq1w8yZM7Fr164Cb46IiIiIDFOYjx+q1nFCaVtbpVshomJI70F19uzZCAkJwZkzZxAYGIjjx48jMDAQp0+fRmhoKL7++uvC7JOIiIiIDIjKyxcA4OjSROFOiKg40ntQTUpKQtu2bTF27Fhcu3YNmZmZuHr1Kj7++GO0bdsWSUlJhdknERERERmQMO8ng6oTr1MlokKQr+eoZmZmYs2aNVizZk1h9UNERERERiAyKBhpySlwdGmqdCtEVAzpfUSViIiIiChbVqYaEf4BcHBuDBNT/khJRAVL779VLC0tMXfuXAQGBuLx48fIzMzUqoyMjMLsk4iIiIgMjMrLF1ZlysC+Xh2lWyGiYkbvU3+XLVsGNzc37Nq1C5s3b0Z6enph9kVEREREBi7sv+epOjV3xr2gYIW7IaLiRO9BtX///vjqq6+wZMmSQmyHiIiIiIxFmM9/g6qLMy5s/0vhboioONH71F9TU1P4+voWZi9EREREZEQeJzxAjCocTs15518iKlh6D6obN27EwIEDC7MXIiIiIjIyKm8/VHKoCRu7Ckq3QkTFiN6n/np5eWHu3LnYuXMnjhw5goSEBJ11duzYUaDNEREREZFhU3n74vX+veHo0hT+J/9Ruh0iKib0HlQ3bNgAAHB0dMTbb7+t87qIcFAlIiIiKmHCvJ9cGubEQZWICpDeg2rt2rULsw8iIiIiMkJRwbeRmvQYjrxOlYgKkN6Danh4eGH2QURERERGSLKyEO7rj9otXGBqboasTLXSLRFRMaD3zZSeVrZsWVSuXFmniIiIiKjkUXn5opSVJWo0eEXpVoiomND7iCoAzJkzB2PHjkWVKlVy3ph5vjZHRERERMWAyvvJ81QdXZoiwj9A4W6IqDjQ+4jq559/js8//xzLli2DiYkJvvnmG8ybNw/BwcEIDQ3Fxx9/XJh9EhEREZGBCvN5MqjyeapEVFD0HlQ//PBDeHh44IcffgAA/PXXX/Dw8ECjRo0QGhqKOnXqFFqTRERERGS4UhOTEBUcCkeXpkq3QkTFhN6Dau3atXHt2jVkZWUhMzMT5cqVA/DksTS//PILRo8eXWhNEhEREZFhU3n5wq56NdhW4X1LiOjl6T2oJiQkwNraGgBw9+5dODv//6kd1tbWKFu2bMF3R0RERERGIey/61SdeFSViAqA3oPqv//+CxcXFwDAnj17MGfOHEyaNAnjxo3DwoULcf78eb0/tGPHjhARnbp8+XL+94CIiIiIFKfy9gUAnv5LRAVC79v0fvfdd3B0dAQAuLu7o3bt2vj+++9hZmaGS5cu4ZNPPsn3h3/44Yfw9/fXfJ2UlJTvbRARERGR8mJU4Uh++Ig3VCKiAqH3oHr58mXNEc+HDx9iwIABsLCwgKWlJRITE1/ow/39/XHp0qUXei8RERERGQ4RQZiPH+q/0RLmFhbITE9XuiUiMmJ6nfpraWmJ4OBg9OzZU2t5enr6Cw+pRERERFS8qLx8YW5hgZqNGijdChEZOb0G1bS0NJQtWxaZmZkF+uF79uxBZmYm7t27hxUrVqB8+fIFun0iIiIiKjrZN1TidapE9LL0vpnS9u3b8d577xXIhz58+BALFy7Ehx9+iK5du2Lp0qUYOnQoTpw4AXNzvc9GJiIiIiIDEu57A1lqNa9TJaKXpvdUePXqVcybNw+HDh3C33//jejoaIiI1jo7duzQa1teXl7w8vLSfH369Gn4+flh3759GDx4MLZt26a1/qRJkzB58mTN1zY2Nvq2TURERERFJC05GfduhXBQJaKXZgJAnrsWALVanefrIvLSR0MTExOxcuVKTJkyJc/1IiIiUKtWrZf6LCIiIiIqeANnTkG7IYMwv8fbSIiMUrodomKvuM5Gek+WtWvXLsw+NJ49SktERERExiPM2w/thgyCk4szB1UiemF6D6rh4eGF2Qf69esHGxsbzSNwiIiIiMj4qLx8AQBOzZ1x/eBRhbshImOl96Cqz+HkiIgIvba1adMmhIaG4tq1a0hKSkKbNm3w5Zdf4vLly/D09NS3JSIiIiIyMHF37iIxLp53/iWil6L3oKpSqZ57Wq6+16j6+/vj/fffx+effw4rKyvcuXMHK1euxNy5c597LSwRERERGbYwb180erMdLEpbIT0lVel2iMgI6T2oDh06VGeZnZ0devbsCRcXF8yaNUvvD/3uu+/w3Xff6b0+ERERERkPlbcfmnbpiJpNGiH0ynWl2yEiI6T3oLp9+/Ycl69YsQLLly/Hq6++is2bNxdYY0RERERknDTXqbo4c1AlohdiWhAb2bVrF0aMGFEQmyIiIiIiIxfhHwh1RiaceJ0qEb2gAhlUnZ2deW0pEREREQEAMtPScDcwiDdUIqIXpvepv1OnTtVZZmFhgcaNG2PgwIFYv359QfZFREREREZM5e0LB+fGqORQE7Hhd5Ruh4iMjN6Dak43P0pLS0NERAQWLVqEBQsWFGhjRERERGS8wrx8geHvwdHFmYMqEeWb3oOqmZlZYfZBRERERMWIytsPAODU3BlX9x1UuBsiMjYFco0qEREREdHTHkRF42F0DG+oREQvRO9B9d13383xOlUAmDJlCgYPHlxgTRERERGR8VN5+8K+fl1YlrFWuhUiMjJ6D6pfffUV0tPTc3wtNTUVX331VYE1RURERETGT+XtC1NTUzg4N1G6FSIyMnoPqvXr14ePj0+Or/n5+aF+/foF1hQRERERGT+Vly+AJ9epEhHlh96DakZGBipWrJjja1WqVIGIFFhTRERERGT87gYEITM9nc9TJaJ803tQPX/+PD7//HOYmJhob8DUFBMmTMCFCxcKvDkiIiIiMl7qjAxE+AfCqVlTnZ8hiYjyovfjadzd3XHmzBn4+flh48aNiIyMRI0aNTBy5Eg4OTnhzTffLMw+iYiIiMgIhXn7oXaLZqhS2xHRoSql2yEiI6H3EdUrV66gS5cuiI+Px/z587F+/XrMmzcPMTEx6NKlC65cuVKYfRIRERGREVJ5PbnHCa9TJaL80PuIKgBcvHgRHTp0gJWVFSpUqICEhASkpqYWVm9EREREZORU3n4AAEcXZ1zatU/hbojIWORrUM2WmpqKe/fuFXQvRERERFTMJMbGIe5OJI+oElG+6H3q7+rVq7F9+/YcX9u2bRtWrlxZYE0RERERUfER5uOHqnWcUNrWVulWiMhI6D2odu/eHZ6enjm+tmvXLri6uhZYU0RERERUfGQ/T9WxWWOFOyEiY6H3oFq1alVER0fn+Nr9+/dhb29fYE0RERERUfER5v1kUHVq3kzhTojIWOg9qEZFRcHFxSXH11q0aIGYmJgCa4qIiIiIio/IoGCkJafA0aWp0q0QkZHQe1D966+/MHv2bLRt21Zrebt27TBz5kzs2rWrwJsjIiIiIuOXlalGhH8AHJwbw8RU7x8/iagE0/tvitmzZyMkJARnzpxBYGAgjh8/jsDAQJw+fRqhoaH4+uuvC7NPIiIiIjJiYd5+sCpTBvb16ijdChEZAb0H1aSkJLRt2xZjx47FtWvXkJmZiatXr+Ljjz9G27ZtkZSUVJh9EhEREZERy76hEh9TQ0T6yNdzVDMzM7FmzRqsWbOmsPohIiIiomIozMcPAODk4owL2/9SuBsiMnT5GlQBoHbt2ujQoQPs7OwQFxeHs2fP4vbt24XRGxEREREVE48THiBGFc4jqkSkF70HVVNTU6xcuRKjRo2C6VMXwWdlZWHdunUYO3YsRKRQmiQiIiIi46fy9sPr/XvDxq4CkuITlG6HiAxYvm6mNGLECHz99deoXbs2Spcujdq1a2PmzJkYOXIkZs2a9UINmJmZwdvbGyKC995774W2QURERESGT/Xf81T5mBoieh69B9WRI0di7ty5+P777xEeHo709HSEh4fjhx9+wNy5czF69OgXamDixImoXLnyC72XiIiIiIxH2H+DqhMHVSJ6Dr0H1WrVquHSpUs5vnbp0iXY29vn+8OrV6+OOXPmYNq0afl+LxEREREZl6jg20hNegxHXqdKRM+h96AaERGBzp075/haly5dEBERke8PX7p0Kfbu3YszZ87k+71EREREZFwkKwvhvv5waNIYpuZmSrdDRAZM75sprV69Gt988w0sLS3x559/IioqCvb29hgyZAgmTJiAGTNm5OuDe/ToAVdXVzRo0ACWlpb5bpyIiIiIjI/KyxevtGmFGg1eQYR/gNLtEJGB0ntQXbhwISpVqoQJEyZg0qRJmuUZGRn48ccfsWjRIr0/1NLSEr/88gs8PDwQFRUFR0fHPNefNGkSJk+erPnaxsZG788iIiIiIsOh8n7yPFVHl6YcVIkoV/l6juq0adPw3Xff4Y033oCdnR3i4+Nx6dIlJCTk7/biM2bMQHp6OpYtW6bX+osXL8bixYs1X7/IacZEREREpLwwnyeDqlNzZ5zdskPhbojIUOVrUAWAhIQEHDp06IU/0MHBAV9++SWGDRuGMmXKAABsbW0BANbW1rC1tcWjR49eePtEREREZLhSE5MQFRzKR9QQUZ70vplSQalduzasrKzg6emJBw8e4MGDB/Dx8QEArF27FtHR0UXdEhEREREVIZWXL+yqV4NtFT6ikIhylu8jqi/Ly8sLnTp10lpmb2+Pbdu2Ye7cuTh27FhRt0RERERERSjM2w+tB/eHk0tT+Bw9qXQ7RGSAinxQffjwIU6fPq21LPtmSjdu3MA///xT1C0RERERURFSefsCeHJDJQ6qRJSTPE/9LVu2bFH1QUREREQlRIwqHMkPH8GpubPSrRCRgcpzUE1ISMDrr78OAFizZg2cnJwKpYmwsDCYmJjgzz//LJTtExEREZHhEBGE+fihZqMGMLewULodIjJAeQ6q6enpKFWqFABg1KhRqFyZF7wTERER0ctTefnC3MICNRs1ULoVIjJAeV6jevPmTcyYMQM7djx5xlXv3r3RsGHDXNfftGlTwXZHRERERMVSmPeT56k6ujTVXLNKRJTNBIDk9mK3bt2wefNmVK5cGSICExOTXDckIjA3L5p7M0VERKBWrVpF8llEREREVPAsra0x//wR+J04gw2TZyjdDpHRKq6zUZ6T5bFjx2Bvbw87OzvExMSgd+/euH79elH1RkRERETFVFpyMu7dCuENlYgoR3odAo2Pj8fo0aNx+fJlxMfHF3ZPRERERFQCqLx80W7IIFSobo+EyCil2yEiA5LnzZSetnHjRsTHx8Pa2hpvvvkmBgwYgDfffBPW1taF2R8RERERFVPZ16k6ufCoKhFp03tQBYCZM2ciKioKJ06cgKenJ06ePImoqCjMmMHrCoiIiIgof1ReT26ixNN/iehZet/9aPz48Zg7dy5WrVqFrVu3IioqCvb29hgyZAg8PDzw8OFDLF++vDB7JSIiIqJiJO7OXSTGxcPRpanSrRCRgdF7UP3000/x008/YerUqZplQUFBOHPmDJKSkvDZZ59xUCUiIiKifAnz9kWjN9vBorQV0lNSlW6HiAyE3qf+Ojk54dChQzm+dvjwYTg6OhZYU0RERERUMqi8/WBmbo6aTRop3QoRGRC9B9X79++jRYsWOb726quv4v79+wXWFBERERGVDJrrVHn6LxE9Re9Tf7ds2QIPDw+kpKTgzz//RGxsLCpVqoQhQ4bA3d0dS5cuLcw+iYiIiKgYivAPhDojk3f+JSIteg+qs2fPRp06dbBs2TIsXboUWVlZMDU1hYmJCbZv347Zs2cXZp9EREREVAxlpqXhbmAQb6hERFr0HlQzMjLw3nvvoUmTJujQoQPs7OwQHx+Pf/75B/7+/oXZIxEREREVYypvXzg4N0Ylh5qIDb+jdDtEZAD0HlSz+fv7czAlIiIiogIT5u0HDH8Pji7OHFSJCEA+bqZERERERFQYNDdUas7rVInoCQ6qRERERKSoB1HReBgdwzv/EpEGB1UiIiIiUpzK2xf29evCsoy10q0QkQHgoEpEREREilN5+8LU1BQOzk2UboWIDIBeg6qlpSVmzZoFFxeXwu6HiIiIiEogXqdKRE/Ta1BNS0vDV199BVtb28Luh4iIiIhKoLsBQchMT+fzVIkIQD5O/b1y5QqcnfkbLiIiIiIqeOqMDET4B8KpWVOYmJgo3Q4RKUzvQXXChAmYMGEChgwZgrJlyxZmT0RERERUAoV5+6G0bVlUqe2odCtEpDBzfVe8cOECzM3NsXnzZgBAeno6RETzuojAxsam4DskIiIiohJB5eUD4H04NXdGdKhK6XaISEF6D6rff/+91mBKRERERFSQVN5+AABHF2dc2rVP4W6ISEl6D6oeHh4F9qGurq6YPn06GjdujHLlyiEqKgpHjhyBu7s7IiMjC+xziIiIiMh4JMbGIe5OJO/8S0Qv9hzVGjVqoE2bNrC2frEHMtvZ2eHSpUv45JNP4Orqinnz5qFHjx44c+YMrKysXmibRERERGT8wnz8ULWOE0rzaRNEJZ7oWx988IFERERIZmamZGZmSosWLQSA7Ny5Uz799FO9t5NTde/eXUREXF1dn7tuRETES30Wi8VisVgsFsswq93QwfKj7wVp2L614r2wWMZQxXU20vuI6qeffooVK1Zgx44d6N+/v9Ztw//55x+89957+m4qR3FxcQCAzMzMl9oOERERERmvMG9fAIBT82YKd0JEStL7GtWJEyfim2++gbu7O0xNtefbmzdvokGDBvn+cFNTU5ibm6Nu3bpYuHAhrl69itOnT+d7O0RERERUPEQGBSMtOQWOLk2VboWIFKT3EVUHB4dch8i0tLQXeraqv78/0tLScOPGDdja2qJPnz5Qq9X53g4RERERFQ9ZmWpE+AfAwbkxTExf6HYqRFQM6P1f/507d+DsnPMd2Fq0aIHQ0NB8f/igQYPQunVruLm5wcbGBsePH4dtDhfOT5o0CREREZri81qJiIiIiq8wbz9YlSkD+3p1lG6FiBSk18WsHh4eEhcXJ7169RIzMzNRq9XSvHlzad68udy7d0+mTp36UhfL1qhRQzIyMmTKlCkl9oJhFovFYrFYLBakccf28qPvBWnz7tuK98JiGXoV19lI7yOq8+bNw6lTp7B//37ExMQAAI4ePYorV67gn3/+waJFi/TdVI7u3r2LqKgo1K1b96W2Q0RERETGLczHDwDg5MLnqRKVVHrfTCkzMxODBg1Cx44d0b17d1SuXBlxcXE4cuQITp069dKN1KlTB9WrV0dISMhLb4uIiIiIjNfjhAeIUYXDqTkHVaKSSu9BNdvp06df+s68u3btwtWrV+Hj44OkpCQ0btwYU6ZMwZ07d7BmzZqX2jYRERERGT+Vtx9e798bNnYVkBSfoHQ7RFTE8j2odunSBa1bt0b16tVx7949XLhwASdOnMjXNi5evIj33nsPU6dOhbm5OcLDw+Hp6Ynvv/8eCQn8i4iIiIiopFN5++L1/r3h6NIU/if/UbodIipieg+qlSpVgqenJ9q3b4/k5GTcv38fVapUQenSpXH+/HkMGjRIc+3q8/zwww/44YcfXrhpIiIiIirewrx9AQBOHFSJSiS9b6b022+/oWHDhujTpw/Kli2LunXromzZsnjrrbfQoEEDLF++vDD7JCIiIqISJCr4NlKTHsOR16kSlUh6D6o9e/bEtGnTcOjQIa3lBw8exFdffYVevXoVeHNEREREVDJJVhbCff3h0KQxTM3NlG6HiIqY3oNqYmIioqOjc3wtKioKjx8/LrCmiIiIiIhUXr4oZWWJGg1eUboVIipieg+qq1evxmeffQYzM+3faJmZmWHChAn4/fffC7w5IiIiIiq5VN5Pnqfq6NJU4U6IqKjleTOlqVOnav5/cnIymjZtipCQEOzdu1dzM6V+/frBxMSkQJ6lSkRERESULcznyaDq5NIUZ7fsULgbIipKJgAktxfVarXeGxIRmJvn+2k3LyQiIgK1atUqks8iIiIiIuVM/esPWFiXxoIeA5VuhcggFdfZKM9Tf83MzPSuohpSiYiIiKjkUHn5wq56NdhWqax0K0RUhPS+RpWIiIiIqKiFef//6b9EVHLk+zBo8+bNUatWLVhZWem8tmMHrx0gIiIiooKj8vYF8OSGSj5HTyrcDREVFb0H1aZNm2Lnzp2oV68eTExMdF4XEQ6qRERERFSgYlThSH74CE7NnZVuhYiKkN6D6u+//4709HT069cPQUFBSE9PL8y+iIiIiIggIgjz8UP9N1rC3MICmfwZlKhE0HtQbdKkCd555x0cOnSoMPshIiIiItKi8vJFow5tUbNRA82pwERUvOl9M6UbN26gfPnyhdgKEREREZGu7BsqOfKGSkQlht6D6hdffIGZM2eiaVP+BUFERERERSfc9way1Gpep0pUguh96u/Zs2dx+PBheHl54f79+0hISNB6XUQ4xBIRERFRgUtLTsa9WyEcVIlKEL0H1YULF2LSpEm4evUqb6ZEREREREVK5eWLdkMGoUJ1eyRERindDhEVMr0H1Q8//BDu7u6YN29eYfZDRERERKQjzNsP7YYMgpOLMwdVohJA72tU1Wo1zp07V5i9EBERERHlSOX15G6/PP2XqGTQe1Ddtm0b+vfvX5i9EBERERHlKO7OXSTGxfPOv0QlhN6n/l6+fBkeHh6oVq0ajh49igcPHuiss2PHjoLsjYiIiIhII8zbF43ebAeL0lZIT0lVuh0iKkQmAESfFdVqdZ6viwjMzfWee19KREQEatWqVSSfRURERESGofMHw9F30qdYPnocQq9cV7odIoNQXGcjvSfL2rVrF2YfRERERER50lyn6tKUgypRMaf3oBoeHl6YfRARERER5SnCPxDqjEw4ufCGSkTFnd6Dqj6HkyMiIl6qGSIiIiKi3GSmpeFuYBBvqERUAug9qKpUKojkfTlrUV2jSkREREQlk8rbFw7OjVHJoSZiw+8o3Q4RFRK9J8uhQ4fqLLOzs0PPnj3h4uKCWbNmFWhjRERERETPCvP2A4a/B0cXZw6qRMWY3oPq9u3bc1y+YsUKLF++HK+++io2b96s17YGDRqEYcOG4bXXXkOlSpWgUqmwfv16LFmyBBkZGfq2REREREQljOaGSs2dcXXfQYW7IaLCYloQG9m1axdGjBih9/pTpkxBWloavvzyS/Tu3RubN2+Gh4cH1qxZUxDtEBEREVEx9SAqGg+jY+DE61SJirUCuajU2dn5uc9Zfdpbb72F2NhYzdenT5+Gqakp5s+fj6lTpyI6Orog2iIiIiKiYkjl7Qvnbp1gWcYaaY+TlW6HiAqB3oPq1KlTdZZZWFigcePGGDhwINavX6/3hz49pGa7evUqAKBatWocVImIiIgoVypvX7i4doGDcxPcunhZ6XaIqBDoPah+9913OsvS0tIQERGBRYsWYcGCBS/VSIcOHZCWloaQkJCX2g4RERERFW9PX6fKQZWoeNJ7UDUzMyu0Jho2bIiJEydi1apVSExM1Hl90qRJmDx5suZrGxubQuuFiIiIiAzb3YAgZKan83mqRMWYCYC8H45ayOzs7HDhwgWkpqaiTZs2SE5+/nUGERERqFWrVhF0R0RERESGaPzGlbCvWxuz2veAiKI/zhIpqrjORnkeUa1cuXK+NhYTE5Ov9cuUKYMDBw7AwsICnTp10mtIJSIiIiIK8/ZD7RbNUKW2I6JDVUq3Q0QFLM9BNSoqSu/fUIkISpUqpfcHW1hY4K+//kKdOnXQvn173Lt3T+/3EhEREVHJpvLyAfA+nJo7c1AlKobyHFQ/+OCDPAdVU1NTDBkyBN27d8/Xh5qammLr1q1o3bo1unTpgqCgoHy9n4iIiIhKNpW3HwDA0cUZl3btU7gbIipoeQ6qGzZsyPW1d955B3PmzEGjRo1w6NAhzJ49W+8PXb58OQYOHIivv/4aZmZmeOONNzSvhYSE5Pj4GiIiIiKibImxcYi7E8kbKhEVY5KfGjRokPj4+EhmZqYcOnRIWrVqla/3A5Dbt29Lbtzc3J77/oiIiHx/JovFYrFYLBareNWw7z3kR98LUtrWVvFeWCylqrjORqbQ04ABA+Dl5YXt27cjOjoaHTp0QM+ePfHvv//quwmN2rVrw8TEJMfK6yguEREREVG27OepOjZrrHAnRFTQnjuo9uvXD9euXYOnpyfi4+PRqVMndO/eHRcuXCiK/oiIiIiIchTm/WRQdWreTOFOiKig5XmN6pUrV9C8eXOcPXsWXbp0wenTp4uqLyIiIiKiPEUGBSM9JZXXqRIVQ3kOqi1atAAAtGrVCgcOHMhzQyICGxubguuMiIiIiCgPWZlqhPvdgINzY5iYmkKyspRuiYgKSJ6DqoeHR1H1QURERESUb2Hefqj3+quwr1cH94KClW6HiApInoPq3Llzi6oPIiIiIqJ8y76hklNzZw6qRMWI3nf9JSIiIiIyNGE+fgAAJxdnhTshooLEQZWIiIiIjNbjhAeIUYXDqTkHVaLihIMqERERERk1lbcfKjnUhI1dBaVbIaICwkGViIiIiIya6r/nqfIxNUTFBwdVIiIiIjJqYf8Nqk4cVImKDQ6qRERERGTUooJvIzXpMRx5nSpRscFBlYiIiIiMmmRlIdzXHw5NGsPU3EzpdoioAHBQJSIiIiKjp/LyRSkrS9Ro8IrSrRBRAeCgSkRERERGT+X95HmqvKESUfHAQZWIiIiIjF6Yz5NBlTdUIioeOKgSERERkdFLTUxCVHAob6hEVExwUCUiIiKiYiHM2w921avBtkplpVshopfEQZWIiIiIigWVF5+nSlRccFAlIiIiomJB5f1kUOUNlYiMHwdVIiIiIioWYlThSH74CE68TpXI6HFQJSIiIqJiQUQQ5uOHmo0awNzCQul2iOglcFAlIiIiomJD5eULcwsL1GzUQOlWiOglcFAlIiIiomIjzPvJ81R5nSqRceOgSkRERETFRrjvDWSp1bxOlcjIcVAlIiIiomIjLTkZ926FcFAlMnKKDKo1atTAsmXLcPHiRaSkpEBElGiDiIiIiIohlZcvbCtXQoXq9kq3QkQvSJFBtV69enjnnXdw//59XLp0SYkWiIiIiKiYyr5O1cmFR1WJjJUig+qZM2dQrVo19OvXDwcPHlSiBSIiIiIqplRevgDA03+JjJgigypP9SUiIiKiwhJ35y4S4+J5518iI8abKRERERFRsRPm7YvqDerDorSV0q0Q0QvgoEpERERExY7K2w9m5uao2aSR0q0Q0QswikF10qRJiIiI0JSNjY3SLRERERGRAdNcp8rTf4mMklEMqosXL0atWrU0lZSUpHRLRERERGTAIvwDoc7I5J1/iYyUUQyqRERERET5kZmWhrs3g3hDJSIjxUGViIiIiIollZcvbOwqoJJDTaVbIaJ8MlfqgwcNGgQAaNq0qdbXN27cQEBAgFJtEREREVExEebtBwx/D44uzogNv6N0O0SUD4oNqjt37szxa3d3d3h4eCjREhEREREVI5obKjV3xtV9BxXuhojyQ7FB1cTERKmPJiIiIqIS4EFUNB5Gx/DOv0RGiNeoEhEREVGxpfL2hX39urAsY610K0SUDxxUiYiIiKjYUnn7wtTUFA7OTZRuhYjygYMqERERERVbT1+nSkTGg4MqERERERVbdwOCkJmezuepEhkZDqpEREREVGypMzIQ4R8Ip2ZNeTNPIiPCQZWIiIiIirUwbz+Uti2LKrUdlW6FiPTEQZWIiIiIijWVlw8AXqdKZEw4qBIRERFRsaby9gMAOLpwUCUyFhxUiYiIiKhYS4yNQ9ydSN5QiciIcFAlIiIiomIvzMcP9nVro7StrdKtEJEezJVuoLBZWlryDm9EVKKICNLS0pRug4jIoIR5++LV3q5wbNYYgWcvKt0OET1HsR1Uu3btikGDBsHGxkbpVoiIilxSUhI8PT1x/PhxpVshIjIIKi9fAIBT82YcVImMQLEcVLt27Yp3330Xv/76K0JCQpCZmal0S0RERcbc3Bx169bFuHHjAIDDKhERgMigYKSnpPI6VSIjUSwH1UGDBuHXX3+Ft7e30q0QESnC29sbv/76K8aMGcNBlYgIQFamGuF+N+Dg3BgmpqaQrCylWyKiPBS7mylZWlrCxsYGISEhSrdCRKSokJAQlC1bFpaWlkq3QkRkEMK8/WBVpgzs69VRuhUieo5iN6hm3ziJp/sSUUmX/fcgbyhHRPTE/1+nyuepEhm6YjeoEhERERHlJMzHDwDg5MJBlcjQcVA1cOvWrUNAQIDSbejlZXr18fHBF198AQC4ffs2fvvtt5f+fBcXF8yZMwdlypTRWq9jx44QEbzxxhv52r6bmxtEBFWrVtUsExFMmzYt370Wtjlz5iAlJaXIP/dF/+yeZqiZ5sbY+iUiKskeJzxAjCqcR1SJjECxvJkSGRcnJyc4Oztj3759AIC3334bDx48yPd25s2bp/U4oubNm8Pd3R0rVqzA48ePNcuvXbuG1q1bw8/P76V7b926NcLDw196O0RERFQ0VN5+eL1/b9jYVUBSfILS7RBRLnhEtYSysrJSugWNfv36ISgoCEFBQQAALy8vqFSqfG8nNDQUPj4+z10vMTERly5d0hpeX9SlS5dw7969l94OFT5zc3OYmvKvPCKikk7l/eQ6VT6mhsiw8ac2IzR//nw8fvwYvXv3RqlSpeDh4YGQkBCkpaXh1q1bGD9+vNb62aeBNm/eHGfOnEFycjKmTZumOQXW1dUV69atw8OHD3Hnzh188803Oj/Q29vbY926dYiOjkZKSgr+/fdfdO7cOc8+bW1t8dtvvyEiIgKpqamIjIzE3r17Ub58ea31+vXrh71792q+fvb00exTetu0aYN///0Xjx8/hpeXF7p06aK1nadP/XVzc8P69esBAFFRURAR3L59G0DOp/5OmDABFy9eREJCAmJjY3HixAm0atUqz/0DdE/7bN26NU6cOIGEhAQkJSUhMDAQX331ldZ7Xn/9dRw+fBiPHj1CYmIidu/eDScnp+d+1tNKlSqF2bNn4+bNm5pst23bBltbW631GjZsiBMnTuDx48cIDAzEkCFDtF7P7XTtlJQUzJkzR/P1yZMncfDgQfTr1w9+fn5ISkrC+fPn4eLikmef1tbWOHz4MEJDQ1GnzovdYbFy5cq4du0arly5gkqVKgEAXnnlFezcuRPx8fFITk7GiRMndHrJ/j6aMGECQkJCkJqailq1aun9/QQAPXr0wNmzZ/H48WPEx8dj48aNqFix4gvtx9OGDBmi+ey4uDgcP34czZs3z/M99vb2WL16NSIjI5Gamorg4GAsWLDgpXshIippwv4bVJ04qBIZNA6qejAxNUXXj9wwdvUydP3IDSYKHZUxMTHBr7/+ivHjx6Nnz544cOAAtm3bhgkTJuCXX35B7969sXXrVixevBgfffSR1nvNzc2xfft2bN++Hb169dIaDH/77TdERkZi4MCBWLduHaZPn44PPvhA83q5cuVw7tw5tG7dGl988QX69++P0NBQHDp0CI0bN861359++gkDBgzArFmz0L17d3z22WeIjIzUOppra2uLDh06aPWTkypVqmDVqlX4+eefMXDgQCQmJuKvv/5ChQoVclz/77//xrx58wAAvXv3RuvWrfH222/nun1HR0esXLkSAwcOxIgRIxAZGYkzZ86gYcOGefb1NBsbGxw4cACpqakYNmwY3nrrLfz4449a18i+/vrrOHPmDDIzMzF8+HCMGDECjo6OOH78OCwsLPT+rB07dmD69OnYsmUL+vbtiwkTJiAlJUXr1GczMzP8+eef2L59OwYMGIAbN25g8+bNeOWVV/T+nKc5Oztj9uzZcHd3x9ChQ1G+fHns3r0bZmZmOa5fvnx5HDt2DDVq1EC7du0QGhqa7890cHDA2bNn8ejRI3Tu3BmxsbFwdHTE+fPnUaNGDYwZMwaDBw9GVlYWTp06pRlks/Xr1w/vvvsupkyZgn79+iEh4ckpXvp8P/Xv3x9///03QkJCMHjwYHz66ado3749du/ene/9eNqkSZOwdetW3LhxA++99x5GjhyJS5cuoUaNGrm+p0KFCjh//jx69eoFDw8P9OrVC/Pnz0eVKlVeqhciopIoKvg2UpMew5HXqRIZtBJzjeoHy35AxVq5/yCYlzLly8HGrgJMTE1Rv9Vr6DDsHTx+8DBf24iLuIu1E758oc8HngyaGzduRLdu3dClSxdcu3YNHTt2xMCBA/HWW29h//79AIDjx4+jbNmy8PDwwJo1ayAimvd7eHjgjz/+0GyzY8eOAIC9e/di5syZmvd369YN7777Ln7//XcAwOeff47KlSujQYMGmtNcjxw5gmvXrmHWrFkYOnRojj2/8cYb2LJli+bIJgB4enpqrdO7d28kJibi3Llzee6/nZ0dunTpAm9vbwCASqVCYGAgevXqhS1btuisHxsbq3mW7rVr1xAdHZ3n9rNv5AQApqamOHLkCF577TWM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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "from matplotlib.pyplot import figure\n", - "%matplotlib inline \n", - "plt.style.use('dark_background')\n", - "def is_val_changed(c_list):\n", - " for indx, val in enumerate(c_list):\n", - " if indx > 0 and val - c_list[indx-1] != 0:\n", - " return True\n", - " return False\n", - "\n", - "tracked_var = \"#pragma omp for simd\"\n", - "tracked_file = 'kernels/field_summary_kernel_c.c'\n", - "f_dict = get_var_occurance_accross_files(tracked_var)\n", - "f_list = get_var_occurance(tracked_var, tracked_file)\n", - "figure(figsize=(12, 6), dpi=80)\n", - "\n", - "sh_hash = [substring[:7] for substring in df[\"git_hash\"]]\n", - "commit_dates = pd.to_datetime(df[\"git_committed_date\"]).dt.strftime(\"%b %d, %Y\")\n", - "ignore_list = [\"kernels/update_halo_kernel_c.c\"]\n", - "for files in f_dict:\n", - " if is_val_changed(f_dict[files]) and files not in ignore_list:\n", - " # print(f_dict[files])\n", - " plt.plot(sh_hash, f_dict[files], marker='.', zorder=2, label=files)\n", - "plt.xticks(sh_hash, commit_dates, rotation=-25, ha=\"left\", rotation_mode=\"anchor\")\n", - "# plt.xticklabels(commit_dates)\n", - "plt.xlabel(\"Commit dates\")\n", - "plt.ylabel(\"Number of occurances\")\n", - "plt.legend(loc=\"lower left\")\n", - "plt.title(tracked_var)\n", - "plt.rcParams.update({'font.size': 14})\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
first-leftdates_xthird_xfirstdates_ythird_y
01521611737245
\n", - "
" - ], - "text/plain": [ - " first-left dates_x third_x first dates_y third_y\n", - "0 1 521 61 1 737 245" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "my_df = pd.DataFrame({\"first-left\":[1,2,45,53],\n", - " \"dates\":[521,82,945,513],\n", - " \"third\":[61,72,245,563]\n", - " })\n", - "second_df = pd.DataFrame({\"first\":[45,2,1,53,45,2,1,53],\n", - " \"dates\":[2556,26,737,348,-1,-1,-1,-1],\n", - " \"third\":[61,72,245,563,0,0,0,0]\n", - " })\n", - "list(my_df)\n", - "df = pd.merge(my_df, second_df, left_on=\"first-left\", right_on=\"first\", how=\"outer\")\n", - "updated_dates = df[df.dates_y != -1]\n", - "updated_dates[updated_dates[\"first\"] == 1]\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "10\n", - "10\n" - ] - }, - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "marker": { - "color": "black" - }, - "name": "hello", - "type": "bar", - "x": [ - "Jul-14,2020(08:44:25)", - "Dec-24,2022(14:14:59)", - "Dec-24,2022(14:14:59)", - "Aug-09,2021(12:23:14)", - "Aug-03,2021(08:49:14)", - "Aug-03,2021(08:49:14)", - "Aug-02,2021(18:40:45)", - "Aug-20,2020(14:20:55)", - "Jul-14,2020(09:16:46)", - "Jul-14,2020(08:13:22)" - ], - "y": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ] - } - ], - "layout": { - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "yaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "zaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - } - } - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import pandas as pd\n", - "import plotly.graph_objects as go\n", - "\n", - "gitFig = go.Figure()\n", - "all_var_list = [{'var_name': 'define', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/update_halo_kernel_c.c', 'occ': 80},{'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/update_halo_kernel_c.c', 'occ': 121}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/viscosity_kernel_c.c', 'occ': 3}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/field_summary_kernel_c.c', 'occ': 2}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/revert_kernel_c.c', 'occ': 5}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/calc_dt_kernel_c.c', 'occ': 3}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/accelerate_kernel_c.c', 'occ': 3}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/ideal_gas_kernel_c.c', 'occ': 2}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/advec_cell_kernel_c.c', 'occ': 15}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/pack_kernel_c.c', 'occ': 12}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/generate_chunk_kernel_c.c', 'occ': 10}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/flux_calc_kernel_c.c', 'occ': 5}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/PdV_kernel_c.c', 'occ': 5}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/reset_field_kernel_c.c', 'occ': 9}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/advec_mom_kernel_c.c', 'occ': 27}, {'var_name': 'pragma', 'hash': 'e37e1d7aab99070a65094e784721b4d05fb86444', 'file_name': 'kernels/initialise_chunk_kernel_c.c', 'occ': 15}]\n", - "\n", - "all_var_df = pd.DataFrame(all_var_list)\n", - "# all_var_df.head()\n", - "# code_senese_df = pd.merge(all_var_df, git_nodes_df, left_on=\"hash\", right_on=\"sha\", how=\"outer\")\n", - "# code_senese_df[\"formatted_date\"] = pd.to_datetime(code_senese_df['date']).dt.strftime(\"%b-%d,%Y(%H:%M:%S)\")\n", - "\n", - "dl = 'Jul-14,2020(08:44:25)','Dec-24,2022(14:14:59)','Dec-24,2022(14:14:59)','Aug-09,2021(12:23:14)','Aug-03,2021(08:49:14)','Aug-03,2021(08:49:14)','Aug-02,2021(18:40:45)','Aug-20,2020(14:20:55)','Jul-14,2020(09:16:46)', 'Jul-14,2020(08:13:22)'\n", - "code_senese_df = {'formatted_date':dl, \"occ\":[1,1,1,1,1,1,1,1,1,1]}\n", - "print(len(code_senese_df[\"formatted_date\"]))\n", - "print(len(code_senese_df[\"occ\"]))\n", - "gitFig.add_trace(go.Bar(x=code_senese_df[\"formatted_date\"], y=code_senese_df['occ'],\n", - " # mode='markers',\n", - " marker = {'color' : 'black'},\n", - " name='hello')\n", - " )\n", - "gitFig.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - ".c\n" - ] - } - ], - "source": [ - "import os\n", - "file_name = \"kernels/advec_mom_kernel_c.c\"\n", - "print(os.path.splitext(file_name)[1])\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'2.2.2'" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "pd.__version__" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.4" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/examples/perf_analyzer/requirements.txt b/examples/perf_analyzer/requirements.txt deleted file mode 100644 index 74ac2b91..00000000 --- a/examples/perf_analyzer/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -aiohttp==3.9.0b0 -diskcache>=5.6.3 -GitPython>=3.1.43 -PyGithub>=2.3.0 -dash>=2.17.0 -pandas>=2.2.0 -dash-bootstrap-components>=1.6.0 \ No newline at end of file diff --git a/examples/perf_analyzer/runner_script.sh b/examples/perf_analyzer/runner_script.sh deleted file mode 100644 index 2a63a90f..00000000 --- a/examples/perf_analyzer/runner_script.sh +++ /dev/null @@ -1,28 +0,0 @@ -if [ -z ${SOURCE_BASE_DIRECTORY+x} ]; then - echo "SOURCE_BASE_DIRECTORY is unset" - exit 0; -else - echo "SOURCE_BASE_DIRECTORY is set to '$SOURCE_BASE_DIRECTORY'"; -fi - -source ~/.bash_profile -conda activate cdsi - -export TAU_PROFILE=1 -export PROFILEDIR=$SOURCE_BASE_DIRECTORY - - -cd $SOURCE_BASE_DIRECTORY -git checkout -f $CANDIDATE_COMMIT_HASH - -cp ../clover.in . - -make clean; -make COMPILER=GNU; -echo "================================ Compile Done ================================ " - -echo "============================= Running CloverLeaf ============================= " -mpirun -np 2 tau_exec $SOURCE_BASE_DIRECTORY/clover_leaf -pprof > tau_results -cd - -conda deactivate \ No newline at end of file diff --git a/examples/perf_analyzer/sample_charting.py b/examples/perf_analyzer/sample_charting.py deleted file mode 100644 index 992e4186..00000000 --- a/examples/perf_analyzer/sample_charting.py +++ /dev/null @@ -1,48 +0,0 @@ -import git -import matplotlib.pyplot as plt -from matplotlib.backend_bases import MouseButton -import pandas as pd - -def main(): - df = pd.read_csv("clover_random_test.csv") - - def on_move(event): - if event.inaxes: - print(f'data coords {event.xdata} {event.ydata},', - f'pixel coords {event.x} {event.y}') - - - def on_click(event): - if event.button is MouseButton.LEFT: - print('disconnecting callback') - plt.disconnect(binding_id) - - - - sh_hash = [substring[:7] for substring in df["git_hash"]] - for col in ["PdV", "Cell Advection", "MPI Halo Exchange", "Self Halo Exchange", "Momentum Advection", "Total"]: - plt.plot(sh_hash, df[col], label=col, linestyle='dashdot') - plt.xticks(rotation=90) - plt.xlabel("Commit (new --> old)") - plt.ylabel("Time (s)") - plt.legend() - - binding_id = plt.connect('motion_notify_event', on_move) - plt.connect('button_press_event', on_click) - - plt.show() - - -def extraTest(): - - current_git_directory = "/tmp/fly_dsi/src" - git_repo = git.Repo(current_git_directory) - - candidate_commit_hash = "-f `b5e598dc0f10ca804dce4a748e3c2314545269cd`" - git_repo.git.reset("HEAD") - git_repo.git.checkout(candidate_commit_hash) - print("hello") - -if __name__ == '__main__': - # main() - extraTest() \ No newline at end of file From fef69ca753cd34277ae281fe26ce080a5bda45b1 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Wed, 15 Jul 2026 12:18:14 -0600 Subject: [PATCH 108/109] VCS: pyproject conflict fix --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c53ca221..eb8ce2ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,4 @@ dsi-vcs = "dsi.utils.version_control.cli:main" [tool.setuptools.packages.find] where = ["."] include = ["dsi*"] -exclude = ["clover3d"] - -[tool.setuptools.package-data] -dsi = ["utils/launch_streamlit.sh","utils/launch_streamlit.bat"] +exclude = ["clover3d"] \ No newline at end of file From 0429821c42a83a2923a3863eb2e1e7243405d839 Mon Sep 17 00:00:00 2001 From: Sayef Azad Sakin Date: Thu, 16 Jul 2026 09:39:48 -0600 Subject: [PATCH 109/109] VCS: merge versioning-update --- dsi/utils/version_control/README.md | 10 - dsi/utils/version_control/dsi_vcs.py | 221 ++++++++++++++++---- dsi/utils/version_control/tests/test_vcs.py | 102 ++++++++- dsi/utils/version_control/vcs_db.py | 26 ++- 4 files changed, 311 insertions(+), 48 deletions(-) diff --git a/dsi/utils/version_control/README.md b/dsi/utils/version_control/README.md index eed0540e..1c35401e 100644 --- a/dsi/utils/version_control/README.md +++ b/dsi/utils/version_control/README.md @@ -6,10 +6,7 @@ - **Full metadata capture**: permissions, ownership, ACLs, extended attributes, and SELinux contexts - **Rsync-based snapshots**: efficient storage with hard-link deduplication -<<<<<<< HEAD -======= - **Merkle commit chain**: SHA-256 commit IDs with per-path Merkle nodes for pruning unchanged subtrees ->>>>>>> upstream/versioning-update - **SQLite database**: structured metadata storage for querying and diffing - **Complete file history**: MD5 hashes, file stats, and all metadata changes tracked @@ -280,14 +277,10 @@ dsi.version("diff") | ------------- | ---------- | --------------------------- | | id | INTEGER PK | Auto-increment ID | | root_folder | TEXT | Repository root path | -<<<<<<< HEAD -| commit_hash | TEXT | UUID4 hex (32 chars) | -======= | commit_hash | TEXT | SHA-256 Merkle commit hash | | root_tree_hash | TEXT | SHA-256 hash of root tree | | parent_commit_hash | TEXT | Previous commit hash | | hash_algorithm | TEXT | Merkle format identifier | ->>>>>>> upstream/versioning-update | committed_at | TEXT | ISO-8601 timestamp | | owner_name | TEXT | Username of the committer | | message | TEXT | Optional commit message | @@ -318,8 +311,6 @@ Stores metadata for each file in each commit. | security_context | TEXT | SELinux context | | symlink_target | TEXT | Target of symlink | -<<<<<<< HEAD -======= ### `merkle_nodes` Table Stores the content-addressed tree node for each committed path, including the synthetic root path `.`. @@ -338,7 +329,6 @@ Stores the content-addressed tree node for each committed path, including the sy | subtree_total_bytes | INTEGER | File bytes below this node | | child_count | INTEGER | Direct child count for this node | ->>>>>>> upstream/versioning-update ### `staging` Table Temporary storage for files to be committed. diff --git a/dsi/utils/version_control/dsi_vcs.py b/dsi/utils/version_control/dsi_vcs.py index 15eede3e..fbcd672f 100755 --- a/dsi/utils/version_control/dsi_vcs.py +++ b/dsi/utils/version_control/dsi_vcs.py @@ -22,15 +22,16 @@ import os import sys -import uuid import subprocess import json import datetime import shutil +import tempfile from typing import Optional from .vcs_db import DB_NAME, SNAPSHOTS_DIR, open_db from .vcs_metadata_helper import collect_metadata, collect_tree_metadata, owner_name +from .merkle import HASH_ALGORITHM, build_merkle_tree, commit_hash as merkle_commit_hash, parent_path # ─────────────────────────── RSYNC SNAPSHOT ────────────────────────────────── @@ -288,16 +289,16 @@ def cmd_commit(self, message: str = ""): # ── Previous snapshot for hard-link deduplication ──────────────────────── prev_row = cur.execute( - "SELECT snapshot_path FROM versions " + "SELECT commit_hash, snapshot_path FROM versions " "WHERE root_folder=? ORDER BY id DESC LIMIT 1", (self.root_folder,) ).fetchone() prev_snapshot = prev_row["snapshot_path"] if prev_row else None + parent_commit_hash = prev_row["commit_hash"] if prev_row else None committed_at = datetime.datetime.now(datetime.timezone.utc).isoformat() - commit_hash = uuid.uuid4().hex running_user = owner_name(os.getuid()) - snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, commit_hash[:12]) + snapshots_root = os.path.join(self.root_folder, SNAPSHOTS_DIR) # ── Validate staged paths before creating the snapshot ───────────────── print(f"Validating {len(staged_rows)} staged path(s)…") @@ -314,35 +315,54 @@ def cmd_commit(self, message: str = ""): sys.exit("No readable staged paths — commit aborted.") # ── Create a complete snapshot of the current repository tree ─────────── - print(f" Creating rsync snapshot → {snapshot_path}") + tmp_snapshot_path = tempfile.mkdtemp(prefix=".tmp-", dir=snapshots_root) + print(f" Creating rsync snapshot → {tmp_snapshot_path}") - ok = rsync_snapshot(self.root_folder, snapshot_path, prev_snapshot) + ok = rsync_snapshot(self.root_folder, tmp_snapshot_path, prev_snapshot) if not ok: conn.close() + shutil.rmtree(tmp_snapshot_path, ignore_errors=True) sys.exit("rsync failed — commit aborted.") try: - apply_snapshot_deletes(snapshot_path, self.root_folder, staged_deletes) + apply_snapshot_deletes(tmp_snapshot_path, self.root_folder, staged_deletes) except ValueError as e: conn.close() - shutil.rmtree(snapshot_path, ignore_errors=True) + shutil.rmtree(tmp_snapshot_path, ignore_errors=True) sys.exit(str(e)) # ── Collect metadata for the complete committed tree ─────────────────── - entries = collect_tree_metadata(snapshot_path, self.root_folder) + entries = collect_tree_metadata(tmp_snapshot_path, self.root_folder) total_bytes = sum(e.get("_st_size") or 0 for e in entries if e.get("file_type") == "file") file_count = sum(1 for e in entries if e.get("file_type") == "file") print(f" {file_count} file(s), {total_bytes:,} bytes") + root_tree_hash, merkle_nodes = build_merkle_tree(entries, tmp_snapshot_path) + commit_hash = merkle_commit_hash( + root_tree_hash=root_tree_hash, + parent_commit_hash=parent_commit_hash, + committed_at=committed_at, + owner_name=running_user, + message=message, + file_count=file_count, + total_bytes=total_bytes, + ) + snapshot_path = os.path.join(snapshots_root, commit_hash[:12]) + if os.path.exists(snapshot_path): + conn.close() + shutil.rmtree(tmp_snapshot_path, ignore_errors=True) + sys.exit(f"Snapshot path already exists for commit prefix: {snapshot_path}") + os.rename(tmp_snapshot_path, snapshot_path) + # ── Insert version row ─────────────────────────────────────────────────── cur.execute( """INSERT INTO versions - (root_folder, commit_hash, committed_at, owner_name, message, - snapshot_path, file_count, total_bytes) - VALUES (?,?,?,?,?,?,?,?)""", - (self.root_folder, commit_hash, committed_at, running_user, message, - snapshot_path, file_count, total_bytes) + (root_folder, commit_hash, root_tree_hash, parent_commit_hash, hash_algorithm, + committed_at, owner_name, message, snapshot_path, file_count, total_bytes) + VALUES (?,?,?,?,?,?,?,?,?,?,?)""", + (self.root_folder, commit_hash, root_tree_hash, parent_commit_hash, HASH_ALGORITHM, + committed_at, running_user, message, snapshot_path, file_count, total_bytes) ) version_id = cur.lastrowid @@ -370,6 +390,27 @@ def cmd_commit(self, message: str = ""): ] ) + merkle_cols = [ + "version_id", "root_folder", "relative_path", "file_type", + "node_hash", "metadata_hash", "content_hash_sha256", + "subtree_file_count", "subtree_total_bytes", "child_count", + ] + merkle_placeholders = ",".join("?" * len(merkle_cols)) + merkle_col_str = ",".join(merkle_cols) + + cur.executemany( + f"INSERT INTO merkle_nodes ({merkle_col_str}) VALUES ({merkle_placeholders})", + [ + tuple( + version_id if c == "version_id" else + self.root_folder if c == "root_folder" else + node.get(c) + for c in merkle_cols + ) + for node in merkle_nodes + ] + ) + # ── Clear staging after a successful commit ────────────────────────────── cur.execute("DELETE FROM staging WHERE root_folder=?", (self.root_folder,)) @@ -397,11 +438,11 @@ def cmd_log(self): print("No versions yet. Run 'commit' first.") return - print(f"{'COMMIT HASH':<34} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") - print("─" * 100) + print(f"{'COMMIT HASH':<66} {'OWNER':<16} {'DATE/TIME (UTC)':<28} {'FILES':>7} {'BYTES':>15} MESSAGE") + print("─" * 132) for r in rows: msg = (r["message"] or "")[:35] - print(f"{r['commit_hash']:<34} {r['owner_name']:<16} {r['committed_at']:<28}" + print(f"{r['commit_hash']:<66} {r['owner_name']:<16} {r['committed_at']:<28}" f"{r['file_count']:>7} {r['total_bytes']:>15,} {msg}") @@ -413,6 +454,12 @@ def get_files_in_root_folder(): files = {} for dirpath, dirnames, filenames in os.walk(root_folder, followlinks=False): dirnames[:] = [d for d in dirnames if d not in {DB_NAME, SNAPSHOTS_DIR}] + if dirpath != root_folder: + abs_path = dirpath + rel_path = os.path.relpath(abs_path, root_folder) + files[rel_path] = collect_metadata(abs_path, self.root_folder) + files[rel_path]["absolute_path"] = abs_path + files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} for fname in filenames: if fname not in {DB_NAME, SNAPSHOTS_DIR}: abs_path = os.path.join(dirpath, fname) @@ -421,38 +468,132 @@ def get_files_in_root_folder(): files[rel_path]["absolute_path"] = abs_path files[rel_path]["lstat"] = json.loads(files[rel_path]["lstat"]) if files[rel_path]["lstat"] else {} return files - - def get_files(chash): - vid = None + + def get_version(chash): if chash == "latest": - vid = conn.execute( - "SELECT id, commit_hash FROM versions WHERE root_folder=? ORDER BY id DESC LIMIT 1", + row = conn.execute( + "SELECT id, commit_hash, snapshot_path, root_tree_hash FROM versions " + "WHERE root_folder=? ORDER BY id DESC LIMIT 1", (root_folder,) ).fetchone() else: - vid = conn.execute( - "SELECT id, commit_hash FROM versions WHERE root_folder = ? AND commit_hash LIKE ?", + row = conn.execute( + "SELECT id, commit_hash, snapshot_path, root_tree_hash FROM versions " + "WHERE root_folder = ? AND commit_hash LIKE ?", (root_folder, chash + "%") ).fetchone() - if not vid: + if not row: conn.close() sys.exit(f"Commit '{chash}' not found.") - rows = conn.execute( + return row + + def get_files(chash): + vid = get_version(chash) + return get_files_for_version(vid) + + def get_files_for_version(version, only_paths: Optional[set[str]] = None): + path_filter = None if only_paths is None else sorted(only_paths) + if path_filter == []: + return {} + + rows = [] + base_sql = ( "SELECT relative_path, absolute_path, file_type, md5_hash, permissions_int, " " owner_name, group_name, lstat " - "FROM file_entries WHERE version_id=?", (vid["id"],) - ).fetchall() + "FROM file_entries WHERE version_id=?" + ) + if path_filter is None: + rows = conn.execute(base_sql, (version["id"],)).fetchall() + else: + for i in range(0, len(path_filter), 500): + chunk = path_filter[i:i + 500] + placeholders = ",".join("?" * len(chunk)) + rows.extend( + conn.execute( + f"{base_sql} AND relative_path IN ({placeholders})", + (version["id"], *chunk), + ).fetchall() + ) + + snapshot_path = version["snapshot_path"] result = {} for r in rows: rec = dict(r) # Unpack the lstat JSON so callers can access st_size, st_mtime, etc. - snapshot_path = os.path.join(self.root_folder, SNAPSHOTS_DIR, vid["commit_hash"][:12]) rec["absolute_path"] = snapshot_target(snapshot_path, r["relative_path"]) rec["lstat"] = json.loads(r["lstat"]) if r["lstat"] else {} result[r["relative_path"]] = rec return result + def get_merkle_nodes(version_id): + rows = conn.execute( + "SELECT relative_path, file_type, node_hash, metadata_hash " + "FROM merkle_nodes WHERE version_id=?", + (version_id,), + ).fetchall() + return {r["relative_path"]: dict(r) for r in rows} + + def children_index(nodes): + children = {path: [] for path in nodes} + for path in nodes: + if path == ".": + continue + children.setdefault(parent_path(path), []).append(path) + return children + + def add_subtree(changed_paths, path, children): + if path != ".": + changed_paths.add(path) + for child_path in children.get(path, []): + add_subtree(changed_paths, child_path, children) + + def changed_paths_from_merkle(v1, v2): + nodes1 = get_merkle_nodes(v1["id"]) + nodes2 = get_merkle_nodes(v2["id"]) + children1 = children_index(nodes1) + children2 = children_index(nodes2) + changed_paths = set() + + def walk(path): + n1 = nodes1.get(path) + n2 = nodes2.get(path) + if n1 and n2 and n1["node_hash"] == n2["node_hash"]: + return + if n1 is None: + add_subtree(changed_paths, path, children2) + return + if n2 is None: + add_subtree(changed_paths, path, children1) + return + + if path != ".": + if n1["file_type"] != n2["file_type"] or n1["metadata_hash"] != n2["metadata_hash"]: + changed_paths.add(path) + elif n1["file_type"] != "dir": + changed_paths.add(path) + + if n1["file_type"] == "dir" or n2["file_type"] == "dir": + child_paths = set(children1.get(path, [])) | set(children2.get(path, [])) + for child_path in sorted(child_paths): + walk(child_path) + + walk(".") + return changed_paths + + def unchanged_count_from_merkle(v1, v2): + row = conn.execute( + "SELECT COUNT(*) AS count " + "FROM merkle_nodes a " + "JOIN merkle_nodes b " + " ON a.relative_path = b.relative_path " + " AND a.node_hash = b.node_hash " + "WHERE a.version_id=? AND b.version_id=? AND a.relative_path <> '.'", + (v1["id"], v2["id"]), + ).fetchone() + return row["count"] if row else 0 + files1 = files2 = {} + unchanged = 0 if c1 is None and c2 is None: files1 = get_files_in_root_folder() files2 = get_files("latest") @@ -463,12 +604,16 @@ def get_files(chash): files1 = get_files(c1) files2 = get_files_in_root_folder() else: - files1 = get_files(c1) - files2 = get_files(c2) + version1 = get_version(c1) + version2 = get_version(c2) + changed_paths = changed_paths_from_merkle(version1, version2) + unchanged = unchanged_count_from_merkle(version1, version2) + files1 = get_files_for_version(version1, changed_paths) + files2 = get_files_for_version(version2, changed_paths) conn.close() all_paths = sorted(set(files1) | set(files2)) - added = deleted = modified = unchanged = 0 + added = deleted = modified = 0 print(f"Diff {c1} → {c2} ({root_folder})\n") print(f"{'STATUS':<10} {'PATH'}") @@ -490,10 +635,14 @@ def get_files(chash): else: f1, f2 = files1[p], files2[p] changes = [] - if f1["md5_hash"] != f2["md5_hash"] and f1["md5_hash"]: - changes.append("content") - result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) - print(f"diff result: {result.stdout.strip()}") + if f1["file_type"] != f2["file_type"]: + changes.append("type") + if f1["md5_hash"] != f2["md5_hash"]: + if f1["md5_hash"] or f2["md5_hash"]: + changes.append("content") + if f1["md5_hash"] and f2["md5_hash"]: + result = subprocess.run(['diff', f1["absolute_path"], f2["absolute_path"]], capture_output=True, text=True) + print(f"diff result: {result.stdout.strip()}") if f1["permissions_int"] != f2["permissions_int"]: changes.append("perms") if f1["owner_name"] != f2["owner_name"] or f1["group_name"] != f2["group_name"]: diff --git a/dsi/utils/version_control/tests/test_vcs.py b/dsi/utils/version_control/tests/test_vcs.py index 6a13c930..d34009f2 100644 --- a/dsi/utils/version_control/tests/test_vcs.py +++ b/dsi/utils/version_control/tests/test_vcs.py @@ -1,4 +1,5 @@ import os +import re import sqlite3 from pathlib import Path from shutil import which @@ -22,9 +23,23 @@ def commits(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row return conn.execute( - "SELECT id, commit_hash, snapshot_path FROM versions ORDER BY id" + "SELECT id, commit_hash, root_tree_hash, parent_commit_hash, " + "hash_algorithm, snapshot_path FROM versions ORDER BY id" ).fetchall() +def merkle_nodes(repo_path, version_id): + with connect_repo(repo_path) as conn: + conn.row_factory = sqlite3.Row + return { + row["relative_path"]: row + for row in conn.execute( + "SELECT relative_path, file_type, node_hash, metadata_hash, " + "content_hash_sha256, subtree_file_count, subtree_total_bytes, child_count " + "FROM merkle_nodes WHERE version_id=? ORDER BY relative_path", + (version_id,), + ).fetchall() + } + def latest_entries(repo_path): with connect_repo(repo_path) as conn: conn.row_factory = sqlite3.Row @@ -55,6 +70,13 @@ def test_add(tmp_path): repo.cmd_commit("beta") alpha, beta = commits(tmp_path) + assert re.fullmatch(r"[0-9a-f]{64}", alpha["commit_hash"]) + assert re.fullmatch(r"[0-9a-f]{64}", beta["commit_hash"]) + assert alpha["hash_algorithm"] == "sha256-merkle-v1" + assert beta["parent_commit_hash"] == alpha["commit_hash"] + assert Path(alpha["snapshot_path"]).name == alpha["commit_hash"][:12] + assert Path(beta["snapshot_path"]).name == beta["commit_hash"][:12] + alpha_path = Path(alpha["snapshot_path"]) beta_path = Path(beta["snapshot_path"]) assert (alpha_path / "a.txt").read_text() == "foo" @@ -67,3 +89,81 @@ def test_add(tmp_path): empty_entry = rows["empty"] assert empty_entry["file_type"] == "dir" assert empty_entry["permissions_int"] == 0o2775 + + beta_nodes = merkle_nodes(tmp_path, beta["id"]) + assert "." in beta_nodes + assert beta_nodes["."]["node_hash"] == beta["root_tree_hash"] + assert beta_nodes["a.txt"]["file_type"] == "file" + assert re.fullmatch(r"[0-9a-f]{64}", beta_nodes["a.txt"]["content_hash_sha256"]) + + +def test_merkle_tracks_content_metadata_symlink_and_deletes(tmp_path): + require_rsync() + repo = Version(str(tmp_path)) + + nested = tmp_path / "nested" + nested.mkdir() + file_path = nested / "data.txt" + file_path.write_text("one") + link_path = tmp_path / "link.txt" + link_path.symlink_to("nested/data.txt") + + repo.cmd_add(["nested", "link.txt"]) + repo.cmd_commit("initial") + first = commits(tmp_path)[0] + first_nodes = merkle_nodes(tmp_path, first["id"]) + + file_path.write_text("two more") + repo.cmd_add(["nested/data.txt"]) + repo.cmd_commit("content") + second = commits(tmp_path)[1] + second_nodes = merkle_nodes(tmp_path, second["id"]) + assert second_nodes["nested/data.txt"]["content_hash_sha256"] != first_nodes["nested/data.txt"]["content_hash_sha256"] + assert second_nodes["nested/data.txt"]["metadata_hash"] == first_nodes["nested/data.txt"]["metadata_hash"] + assert second["root_tree_hash"] != first["root_tree_hash"] + + file_path.chmod(0o640) + repo.cmd_add(["nested/data.txt"]) + repo.cmd_commit("metadata") + third = commits(tmp_path)[2] + third_nodes = merkle_nodes(tmp_path, third["id"]) + assert third_nodes["nested/data.txt"]["content_hash_sha256"] == second_nodes["nested/data.txt"]["content_hash_sha256"] + assert third_nodes["nested/data.txt"]["metadata_hash"] != second_nodes["nested/data.txt"]["metadata_hash"] + + link_path.unlink() + link_path.symlink_to("missing.txt") + repo.cmd_add(["link.txt"]) + repo.cmd_commit("symlink") + fourth = commits(tmp_path)[3] + fourth_nodes = merkle_nodes(tmp_path, fourth["id"]) + assert fourth_nodes["link.txt"]["metadata_hash"] != third_nodes["link.txt"]["metadata_hash"] + + repo.cmd_delete(["nested/data.txt"]) + repo.cmd_commit("delete") + fifth = commits(tmp_path)[4] + fifth_nodes = merkle_nodes(tmp_path, fifth["id"]) + assert "nested/data.txt" not in fifth_nodes + assert fifth["parent_commit_hash"] == fourth["commit_hash"] + + +def test_diff_short_circuits_equal_root_trees(tmp_path, capsys): + require_rsync() + repo = Version(str(tmp_path)) + + (tmp_path / "a.txt").write_text("same") + repo.cmd_add(["a.txt"]) + repo.cmd_commit("first") + + repo.cmd_delete(["does-not-exist.txt"]) + repo.cmd_commit("same tree") + + first, second = commits(tmp_path) + assert first["root_tree_hash"] == second["root_tree_hash"] + assert first["commit_hash"] != second["commit_hash"] + + repo.cmd_diff(first["commit_hash"][:12], second["commit_hash"][:12]) + out = capsys.readouterr().out + assert "ADDED" not in out + assert "DELETED" not in out + assert "MODIFIED" not in out + assert "Summary: +0 added -0 deleted ~0 modified" in out diff --git a/dsi/utils/version_control/vcs_db.py b/dsi/utils/version_control/vcs_db.py index 989ce0da..2724cd7a 100644 --- a/dsi/utils/version_control/vcs_db.py +++ b/dsi/utils/version_control/vcs_db.py @@ -12,7 +12,10 @@ CREATE TABLE IF NOT EXISTS versions ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL, - commit_hash TEXT NOT NULL, -- UUID4, stored as 32-char hex + commit_hash TEXT NOT NULL, -- SHA-256 Merkle commit hash + root_tree_hash TEXT NOT NULL, + parent_commit_hash TEXT, + hash_algorithm TEXT NOT NULL, committed_at TEXT NOT NULL, -- ISO-8601 timestamp owner_name TEXT NOT NULL, -- Username of the committer message TEXT, @@ -65,6 +68,27 @@ CREATE INDEX IF NOT EXISTS idx_file_entries_path ON file_entries(root_folder, relative_path); +CREATE TABLE IF NOT EXISTS merkle_nodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version_id INTEGER NOT NULL REFERENCES versions(id) ON DELETE CASCADE, + root_folder TEXT NOT NULL, + relative_path TEXT NOT NULL, + file_type TEXT NOT NULL, + node_hash TEXT NOT NULL, + metadata_hash TEXT NOT NULL, + content_hash_sha256 TEXT, + subtree_file_count INTEGER NOT NULL, + subtree_total_bytes INTEGER NOT NULL, + child_count INTEGER NOT NULL, + UNIQUE(version_id, relative_path) +); + +CREATE INDEX IF NOT EXISTS idx_merkle_nodes_root_path + ON merkle_nodes(root_folder, version_id, relative_path); + +CREATE INDEX IF NOT EXISTS idx_merkle_nodes_hash + ON merkle_nodes(root_folder, node_hash); + CREATE TABLE IF NOT EXISTS staging ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_folder TEXT NOT NULL,