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.
- duckboard-0.1.0/.gitignore +55 -0
- duckboard-0.1.0/CHANGELOG.md +34 -0
- duckboard-0.1.0/PKG-INFO +121 -0
- duckboard-0.1.0/README.md +91 -0
- duckboard-0.1.0/examples/sample.csv +5 -0
- duckboard-0.1.0/pyproject.toml +63 -0
- duckboard-0.1.0/src/duckboard/__init__.py +11 -0
- duckboard-0.1.0/src/duckboard/catalog.py +105 -0
- duckboard-0.1.0/src/duckboard/cli.py +62 -0
- duckboard-0.1.0/src/duckboard/commands.py +192 -0
- duckboard-0.1.0/src/duckboard/exceptions.py +13 -0
- duckboard-0.1.0/src/duckboard/formatter.py +60 -0
- duckboard-0.1.0/src/duckboard/repl.py +95 -0
- duckboard-0.1.0/src/duckboard/session.py +53 -0
- duckboard-0.1.0/tests/test_catalog.py +149 -0
- duckboard-0.1.0/tests/test_cli.py +51 -0
- duckboard-0.1.0/tests/test_commands.py +175 -0
- duckboard-0.1.0/tests/test_formatter.py +72 -0
- duckboard-0.1.0/tests/test_repl.py +108 -0
- duckboard-0.1.0/tests/test_session.py +8 -0
- duckboard-0.1.0/tests/test_smoke.py +63 -0
|
@@ -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
|
duckboard-0.1.0/PKG-INFO
ADDED
|
@@ -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,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,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()
|