feat(ipython): add data helpers without persisted history (#510)

Add a dedicated IPython module with startup helpers for JSON, YAML, TOML, and CSV plus a small set of common standard-library imports for interactive use.

Disable SQLite history and stop preserving ~/.ipython: interactive sessions can contain credentials and other sensitive data, so that profile state must not survive reboots implicitly.

Assisted-by: DeepSeek Harness 0.2.2-alpha.2 (deepseek-flash)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-11-02 14:45:20 +08:00
1 parent 179b20a42c
commit 7ea9fd3197
7 files changed
+140 -4

No files matched your search

+2 -2
View File
@@ -7,5 +7,5 @@ closure.
Editor programs, keymaps, `$EDITOR` defaults, and usage docs live under
[`../../core/editors/`](../../core/editors/README.md) (Helix, Neovim backup, glossary, cheatsheets).
[`default.nix`](./default.nix) imports `./packages.nix` so `home/base/tui` can keep pulling in
tooling without mixing it into `core/editors`.
[`default.nix`](./default.nix) imports [`ipython/`](./ipython/) and `./packages.nix` so
`home/base/tui` can keep pulling in tooling without mixing it into `core/editors`.
+4 -1
View File
@@ -1,4 +1,7 @@
# Editor-related language servers and heavy tooling — kept out of `core/editors`.
{
imports = [ ./packages.nix ];
imports = [
./ipython
./packages.nix
];
}
+13
View File
@@ -0,0 +1,13 @@
# IPython
[`default.nix`](./default.nix) manages `~/.ipython/profile_default` with two pieces:
- `ipython_config.py` disables SQLite history because interactive sessions may contain secrets.
- `startup/10-data-helpers.py` loads JSON, YAML, TOML, and CSV helpers at every IPython start. It
also pre-imports a small set of common standard-library names such as `Path`, `Counter`, and
`datetime`.
[`data-helpers.py`](./data-helpers.py) deliberately keeps heavier libraries such as NumPy and pandas
out of startup; import them explicitly when needed. The `~/.ipython` profile is not persisted by
`hosts/idols-ai/preservation.nix`, so Home Manager recreates the config and helpers from the Nix
store on every activation.
@@ -0,0 +1,102 @@
# ruff: noqa: F401
"""IPython startup helpers for inspecting structured data.
IPython executes files in this profile's startup directory before the first
prompt, so these helpers are available in every interactive session.
"""
# A small set of common names preloaded for interactive use. The unused-import
# exception is intentional: IPython exposes these names directly in the prompt.
import csv
import io
import json
import os
import re
import shutil
import subprocess
import sys
import tomllib
from collections import Counter, defaultdict
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
from pprint import pprint
import toml
import yaml
def loads_json(text: str):
return json.loads(text)
def dumps_json(value, indent: int = 2) -> str:
return json.dumps(value, ensure_ascii=False, indent=indent)
def loads_yaml(text: str):
return yaml.safe_load(text)
def dumps_yaml(value) -> str:
return yaml.safe_dump(value, allow_unicode=True, sort_keys=False)
def loads_toml(text: str):
return tomllib.loads(text)
def dumps_toml(value) -> str:
return toml.dumps(value)
def loads_csv(text: str) -> list[dict[str, str]]:
return list(csv.DictReader(io.StringIO(text)))
def dumps_csv(rows, fieldnames=None) -> str:
rows = list(rows)
if not rows:
return ""
fieldnames = fieldnames or list(rows[0])
buffer = io.StringIO(newline="")
writer = csv.DictWriter(buffer, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
return buffer.getvalue()
def show(value):
"""Pretty-print a value and return it for further use."""
pprint(value, width=120, sort_dicts=False)
return value
def show_json(text: str):
return show(loads_json(text))
def show_yaml(text: str):
return show(loads_yaml(text))
def show_toml(text: str):
return show(loads_toml(text))
def show_csv(text: str):
return show(loads_csv(text))
def copy_to_clipboard(text: str) -> str:
"""Copy text to the system clipboard and return it."""
commands = (
[["pbcopy"]]
if sys.platform == "darwin"
else [["wl-copy"], ["xclip", "-selection", "clipboard"]]
)
for command in commands:
if shutil.which(command[0]):
subprocess.run(command, input=text, text=True, check=True)
return text
raise RuntimeError("No clipboard command found (need wl-copy, xclip, or pbcopy)")
+14
View File
@@ -0,0 +1,14 @@
{ ... }:
{
# Interactive IPython sessions routinely handle credentials and other
# sensitive values, so keep the SQLite history disabled rather than relying
# on the profile directory's lifetime. Home Manager recreates these files
# from the Nix store on every activation.
home.file = {
".ipython/profile_default/ipython_config.py".text = ''
c = get_config()
c.HistoryManager.enabled = False
'';
".ipython/profile_default/startup/10-data-helpers.py".source = ./data-helpers.py;
};
}
+1
View File
@@ -71,6 +71,7 @@
requests
pyquery
pyyaml
toml # TOML writing; reading uses stdlib tomllib
protobuf # protocol buffer compiler
]
))
+4 -1
View File
@@ -216,7 +216,10 @@ in
".config/Joplin" # joplin-desktop
".local/share/jupyter"
".ipython"
# Deliberately not persisted: ~/.ipython contains history.sqlite, and
# interactive sessions may contain credentials or other sensitive
# values. Home Manager recreates the profile config and startup helpers
# from the Nix store on every activation.
# ======================================
# Cloud Native