duckboard 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+
28
+ # Testing / coverage
29
+ .pytest_cache/
30
+ .coverage
31
+ htmlcov/
32
+ .mypy_cache/
33
+ .ruff_cache/
34
+
35
+ # IDE
36
+ .vscode/
37
+ .idea/
38
+ *.swp
39
+ *.swo
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Duckboard local state
46
+ .duckboard/
47
+
48
+ # Temp / debug artifacts
49
+ debug_*.py
50
+ tmp_*/
51
+ tmp_*
52
+ followers.json
53
+
54
+ # Mangled test output (from failed Windows path writes)
55
+ Users*/
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to duckboard are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
+
6
+ ## [0.1.0] - 2026-08-20
7
+
8
+ ### Added
9
+ - `DuckboardSession` core engine with DuckDB connection management
10
+ - `FileCatalog` — register files as queryable DuckDB views
11
+ - Supported formats: CSV, TSV, PSV (pipe-separated), Parquet, JSON, JSONL, NDJSON
12
+ - `formatter.py` — box-drawing terminal output with numeric right-alignment,
13
+ NULL rendering, 50-row display cap, and row count footer
14
+ - Interactive REPL (`repl.py`) with:
15
+ - Semicolon-terminated multiline SQL
16
+ - `:command` dispatch
17
+ - readline history via `pyreadline3` (optional dep, Windows)
18
+ - Clean exit on `:quit`, `:q`, `exit`, `quit`, Ctrl+D
19
+ - REPL commands: `:load`, `:tables`, `:schema`, `:save`, `:unload`, `:pwd`
20
+ - `:load` — quoted path support, optional `as name` (defaults to filename stem)
21
+ - `:save` — auto-detect format from extension, explicit `--csv/--parquet/--json` override
22
+ - Large export warning prompt (2,000+ rows)
23
+ - Windows path normalization throughout (backslash → forward slash)
24
+ - `cli.py` entrypoint with `--version` and `--help`
25
+ - 45 tests passing across catalog, session, formatter, REPL, commands, CLI, and smoke test
26
+
27
+ ## [Unreleased] - v0.2.0
28
+
29
+ ### Planned
30
+ - CSV/PSV/TSV validation on `:load` (column count mismatch detection)
31
+ - No-header detection with `--no-header` flag and column name prompt
32
+ - Malformed row storage in `_errors_{name}` session table
33
+ - `:export_errors <table>` and `:export_clean <table>` commands
34
+ - `:tables` warning indicator for tables with errors
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.5
2
+ Name: duckboard
3
+ Version: 0.1.0
4
+ Summary: File-first local SQL workspace for CSV, PSV, Parquet, and JSON — powered by DuckDB.
5
+ Project-URL: Homepage, https://github.com/OmUniyal/duckboard
6
+ Project-URL: Repository, https://github.com/OmUniyal/duckboard
7
+ Project-URL: Issues, https://github.com/OmUniyal/duckboard/issues
8
+ Author: Om Uniyal
9
+ License-Expression: MIT
10
+ Keywords: analytics,cli,csv,duckdb,parquet,repl,sql
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Database
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: duckdb>=1.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
27
+ Provides-Extra: readline
28
+ Requires-Dist: pyreadline3>=3.0; (sys_platform == 'win32') and extra == 'readline'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # duckboard
32
+
33
+ File-first local SQL workspace for CSV, Parquet, PSV, and JSON — powered by [DuckDB](https://duckdb.org/).
34
+
35
+ Load files once, query by name with plain SQL, export results. Terminal-native alternative to spinning up a notebook for quick file questions.
36
+
37
+ > **Status:** Alpha — core functionality complete, PyPI release coming soon.
38
+
39
+ ## Install
40
+
41
+ ```powershell
42
+ pip install duckboard
43
+ ```
44
+
45
+ **Development install:**
46
+
47
+ ```powershell
48
+ git clone https://github.com/OmUniyal/duckboard
49
+ cd duckboard
50
+ python -m venv .venv
51
+ .\.venv\Scripts\Activate.ps1
52
+ pip install -e ".[dev]"
53
+ ```
54
+
55
+ ## Quickstart
56
+
57
+ ```powershell
58
+ duckboard
59
+ ```
60
+
61
+ ```
62
+ duckboard> :load examples/sample.csv as sample
63
+ duckboard> SELECT color, COUNT(*) AS n FROM sample GROUP BY 1;
64
+ ┌────────┬───┐
65
+ │ color │ n │
66
+ ├────────┼───┤
67
+ │ blue │ 1 │
68
+ │ green │ 1 │
69
+ │ red │ 2 │
70
+ └────────┴───┘
71
+ (3 rows)
72
+ duckboard> :save results.csv
73
+ Saved 3 rows to results.csv
74
+ duckboard> :quit
75
+ Bye.
76
+ ```
77
+
78
+ ## Supported file formats
79
+
80
+ | Extension | Format |
81
+ |------------------------|---------|
82
+ | `.csv`, `.tsv` | CSV |
83
+ | `.psv` | PSV (pipe-separated) |
84
+ | `.parquet` | Parquet |
85
+ | `.json`, `.jsonl`, `.ndjson` | JSON |
86
+
87
+ ## REPL commands
88
+
89
+ | Command | Description |
90
+ |---------|-------------|
91
+ | `:load "path/to/file.ext" [as name]` | Load a file as a queryable table. Name defaults to filename stem. |
92
+ | `:tables` | List all loaded tables with format and path. |
93
+ | `:schema <table>` | Show column names, types, and nullability for a table. |
94
+ | `:save "path/to/output.ext" [--csv\|--parquet\|--json]` | Save last query result to a file. Format auto-detected from extension; use flag to override. |
95
+ | `:unload <table>` | Remove a loaded table from the session. |
96
+ | `:pwd` | Show current working directory. |
97
+ | `:quit` / `:q` / `exit` / `quit` / `Ctrl+D` | Exit duckboard. |
98
+
99
+ ## Notes
100
+
101
+ - Queries display a maximum of 50 rows in the terminal. Full results are always exported via `:save`.
102
+ - Large exports (2,000+ rows) prompt for confirmation before writing.
103
+ - On Windows, use forward slashes in paths: `:load data/sales.csv` not `:load data\sales.csv`.
104
+ - Multi-line SQL is supported — statements execute on semicolon.
105
+
106
+ ## Project layout
107
+
108
+ ```
109
+ src/duckboard/
110
+ ├── session.py # DuckboardSession — owns DuckDB connection + state
111
+ ├── catalog.py # Registered file → view mappings
112
+ ├── repl.py # Interactive REPL loop
113
+ ├── commands.py # :load, :tables, :schema, :save, :unload, :pwd
114
+ ├── formatter.py # Box-drawing table output for query results
115
+ ├── cli.py # CLI entry point
116
+ └── exceptions.py # DuckboardError hierarchy
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,91 @@
1
+ # duckboard
2
+
3
+ File-first local SQL workspace for CSV, Parquet, PSV, and JSON — powered by [DuckDB](https://duckdb.org/).
4
+
5
+ Load files once, query by name with plain SQL, export results. Terminal-native alternative to spinning up a notebook for quick file questions.
6
+
7
+ > **Status:** Alpha — core functionality complete, PyPI release coming soon.
8
+
9
+ ## Install
10
+
11
+ ```powershell
12
+ pip install duckboard
13
+ ```
14
+
15
+ **Development install:**
16
+
17
+ ```powershell
18
+ git clone https://github.com/OmUniyal/duckboard
19
+ cd duckboard
20
+ python -m venv .venv
21
+ .\.venv\Scripts\Activate.ps1
22
+ pip install -e ".[dev]"
23
+ ```
24
+
25
+ ## Quickstart
26
+
27
+ ```powershell
28
+ duckboard
29
+ ```
30
+
31
+ ```
32
+ duckboard> :load examples/sample.csv as sample
33
+ duckboard> SELECT color, COUNT(*) AS n FROM sample GROUP BY 1;
34
+ ┌────────┬───┐
35
+ │ color │ n │
36
+ ├────────┼───┤
37
+ │ blue │ 1 │
38
+ │ green │ 1 │
39
+ │ red │ 2 │
40
+ └────────┴───┘
41
+ (3 rows)
42
+ duckboard> :save results.csv
43
+ Saved 3 rows to results.csv
44
+ duckboard> :quit
45
+ Bye.
46
+ ```
47
+
48
+ ## Supported file formats
49
+
50
+ | Extension | Format |
51
+ |------------------------|---------|
52
+ | `.csv`, `.tsv` | CSV |
53
+ | `.psv` | PSV (pipe-separated) |
54
+ | `.parquet` | Parquet |
55
+ | `.json`, `.jsonl`, `.ndjson` | JSON |
56
+
57
+ ## REPL commands
58
+
59
+ | Command | Description |
60
+ |---------|-------------|
61
+ | `:load "path/to/file.ext" [as name]` | Load a file as a queryable table. Name defaults to filename stem. |
62
+ | `:tables` | List all loaded tables with format and path. |
63
+ | `:schema <table>` | Show column names, types, and nullability for a table. |
64
+ | `:save "path/to/output.ext" [--csv\|--parquet\|--json]` | Save last query result to a file. Format auto-detected from extension; use flag to override. |
65
+ | `:unload <table>` | Remove a loaded table from the session. |
66
+ | `:pwd` | Show current working directory. |
67
+ | `:quit` / `:q` / `exit` / `quit` / `Ctrl+D` | Exit duckboard. |
68
+
69
+ ## Notes
70
+
71
+ - Queries display a maximum of 50 rows in the terminal. Full results are always exported via `:save`.
72
+ - Large exports (2,000+ rows) prompt for confirmation before writing.
73
+ - On Windows, use forward slashes in paths: `:load data/sales.csv` not `:load data\sales.csv`.
74
+ - Multi-line SQL is supported — statements execute on semicolon.
75
+
76
+ ## Project layout
77
+
78
+ ```
79
+ src/duckboard/
80
+ ├── session.py # DuckboardSession — owns DuckDB connection + state
81
+ ├── catalog.py # Registered file → view mappings
82
+ ├── repl.py # Interactive REPL loop
83
+ ├── commands.py # :load, :tables, :schema, :save, :unload, :pwd
84
+ ├── formatter.py # Box-drawing table output for query results
85
+ ├── cli.py # CLI entry point
86
+ └── exceptions.py # DuckboardError hierarchy
87
+ ```
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,5 @@
1
+ id,color,amount
2
+ 1,red,10.5
3
+ 2,blue,20.0
4
+ 3,red,15.25
5
+ 4,green,8.0
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "duckboard"
7
+ version = "0.1.0"
8
+ description = "File-first local SQL workspace for CSV, PSV, Parquet, and JSON — powered by DuckDB."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Om Uniyal" },
14
+ ]
15
+ keywords = ["duckdb", "sql", "csv", "parquet", "cli", "repl", "analytics"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Database",
25
+ "Topic :: Utilities",
26
+ ]
27
+ dependencies = [
28
+ "duckdb>=1.0.0",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8.0.0",
34
+ "pytest-cov>=5.0.0",
35
+ "ruff>=0.5.0",
36
+ "mypy>=1.10.0",
37
+ ]
38
+ readline = ["pyreadline3>=3.0; sys_platform == 'win32'"]
39
+
40
+ [project.scripts]
41
+ duckboard = "duckboard.cli:main"
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/OmUniyal/duckboard"
45
+ Repository = "https://github.com/OmUniyal/duckboard"
46
+ Issues = "https://github.com/OmUniyal/duckboard/issues"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/duckboard"]
50
+
51
+ [tool.ruff]
52
+ line-length = 100
53
+ target-version = "py310"
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "UP", "B"]
57
+
58
+ [tool.mypy]
59
+ python_version = "3.10"
60
+ strict = true
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests"]
@@ -0,0 +1,11 @@
1
+ """duckboard: file-first local SQL workspace powered by DuckDB."""
2
+
3
+ from duckboard.exceptions import DuckboardError
4
+ from duckboard.session import DuckboardSession
5
+
6
+ __version__ = "0.0.0"
7
+
8
+ __all__ = [
9
+ "DuckboardSession",
10
+ "DuckboardError",
11
+ ]
@@ -0,0 +1,105 @@
1
+ """File catalog — register paths as queryable table/view names."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import duckdb
10
+
11
+ from duckboard.exceptions import CatalogError
12
+
13
+ _NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
14
+
15
+ _CSV_EXTENSIONS = {".csv", ".tsv"}
16
+ _PSV_EXTENSIONS = {".psv"}
17
+ _PARQUET_EXTENSIONS = {".parquet"}
18
+ _JSON_EXTENSIONS = {".json", ".jsonl", ".ndjson"}
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class CatalogEntry:
23
+ """One registered file."""
24
+
25
+ name: str
26
+ path: Path
27
+ format: str # "csv", "psv", "parquet", or "json"
28
+
29
+
30
+ def _validate_name(name: str) -> None:
31
+ if not _NAME_PATTERN.match(name):
32
+ raise CatalogError(
33
+ f"Invalid table name {name!r}. Use letters, numbers, underscore; "
34
+ "must not start with a number."
35
+ )
36
+
37
+
38
+ def _detect_format(path: Path) -> str:
39
+ ext = path.suffix.lower()
40
+ if ext in _CSV_EXTENSIONS:
41
+ return "csv"
42
+ if ext in _PSV_EXTENSIONS:
43
+ return "psv"
44
+ if ext in _PARQUET_EXTENSIONS:
45
+ return "parquet"
46
+ if ext in _JSON_EXTENSIONS:
47
+ return "json"
48
+ raise CatalogError(
49
+ f"Unsupported file type {ext!r} for {path}. "
50
+ "Supported: .csv, .tsv, .psv, .parquet, .json, .jsonl, .ndjson"
51
+ )
52
+
53
+
54
+ def _read_function(fmt: str, path: Path) -> str:
55
+ """Return DuckDB read_* SQL for a file path."""
56
+ # Forward slashes keep Windows paths safe inside SQL string literals.
57
+ path_literal = str(path.resolve()).replace("\\", "/").replace("'", "''")
58
+ if fmt == "csv":
59
+ return f"read_csv_auto('{path_literal}')"
60
+ if fmt == "psv":
61
+ return f"read_csv_auto('{path_literal}', sep='|')"
62
+ if fmt == "parquet":
63
+ return f"read_parquet('{path_literal}')"
64
+ if fmt == "json":
65
+ return f"read_json_auto('{path_literal}')"
66
+ raise CatalogError(f"Unknown format: {fmt}")
67
+
68
+
69
+ class FileCatalog:
70
+ """Register files as DuckDB views queryable by name."""
71
+
72
+ def __init__(self, conn: duckdb.DuckDBPyConnection) -> None:
73
+ self._conn = conn
74
+ self._entries: dict[str, CatalogEntry] = {}
75
+
76
+ def load(self, name: str, path: str | Path) -> CatalogEntry:
77
+ _validate_name(name)
78
+
79
+ resolved = Path(path).expanduser().resolve()
80
+ if not resolved.is_file():
81
+ raise CatalogError(f"File not found: {resolved}")
82
+
83
+ fmt = _detect_format(resolved)
84
+ read_fn = _read_function(fmt, resolved)
85
+
86
+ self._conn.execute(f"CREATE OR REPLACE VIEW {name} AS SELECT * FROM {read_fn}")
87
+
88
+ entry = CatalogEntry(name=name, path=resolved, format=fmt)
89
+ self._entries[name] = entry
90
+ return entry
91
+
92
+ def list_tables(self) -> list[CatalogEntry]:
93
+ return sorted(self._entries.values(), key=lambda e: e.name)
94
+
95
+ def get(self, name: str) -> CatalogEntry:
96
+ try:
97
+ return self._entries[name]
98
+ except KeyError as exc:
99
+ raise CatalogError(f"Table {name!r} is not loaded.") from exc
100
+
101
+ def unload(self, name: str) -> None:
102
+ if name not in self._entries:
103
+ raise CatalogError(f"No table named '{name}' is loaded.")
104
+ self._conn.execute(f"DROP VIEW IF EXISTS {name}")
105
+ del self._entries[name]
@@ -0,0 +1,62 @@
1
+ """CLI entrypoint — parses args and launches the REPL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from duckboard import __version__
9
+
10
+ from duckboard.repl import run_repl
11
+ from duckboard.session import DuckboardSession
12
+
13
+
14
+ HELP_EPILOG = """
15
+ REPL commands:
16
+ :load "path/to/file.ext" [as name] Load a file as a queryable table
17
+ :tables List all loaded tables
18
+ :schema <table> Show column types for a table
19
+ :save "path/to/output.ext" Save last query result to file
20
+ [--csv|--parquet|--json]
21
+ :unload <table> Remove a loaded table
22
+ :pwd Show current working directory
23
+ :quit / :q / exit / quit / Ctrl+D Exit duckboard
24
+
25
+ links:
26
+ Repo: https://github.com/OmUniyal/duckboard
27
+
28
+ examples:
29
+ duckboard Launch the interactive REPL
30
+ duckboard --version Print version and exit
31
+ """
32
+
33
+
34
+ def _build_parser() -> argparse.ArgumentParser:
35
+ parser = argparse.ArgumentParser(
36
+ prog="duckboard",
37
+ description=(
38
+ "duckboard — file-first local SQL workspace powered by DuckDB.\n\n"
39
+ "Load CSV, Parquet, and JSON files and query them with plain SQL\n"
40
+ "in an interactive terminal session. No database setup required."
41
+ ),
42
+ epilog=HELP_EPILOG,
43
+ formatter_class=argparse.RawDescriptionHelpFormatter,
44
+ )
45
+ parser.add_argument(
46
+ "--version",
47
+ action="version",
48
+ version=f"duckboard {__version__}",
49
+ )
50
+ return parser
51
+
52
+
53
+ def main() -> None:
54
+ parser = _build_parser()
55
+ parser.parse_args()
56
+
57
+ with DuckboardSession() as session:
58
+ run_repl(session)
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()