Modern data platform - PostgreSQL admin + Analytics engine
Project description
Tusk
Modern Data Platform — SQL Client, PostgreSQL Admin, Analytics Engine, ETL Pipeline Builder & Plugin System
Built with Claude: This project was developed with Claude Code (Anthropic's AI assistant).
Features
SQL Client (Studio)
- Multi-connection support (PostgreSQL, SQLite, DuckDB)
- Tabbed SQL editor with CodeMirror 6 (syntax highlighting, autocomplete)
- Schema browser with FK/PK indicators
- Query history and saved queries with folders
- Results grid with sortable, resizable columns
- Export to CSV/JSON
- Keyboard shortcuts (Ctrl+Enter, Ctrl+S, Ctrl+N, etc.)
PostgreSQL Admin
- Server statistics dashboard with auto-refresh
- Active queries monitor with kill button
- Locks monitor with blocking visualization
- Backup/Restore with pg_dump/pg_restore
- Table maintenance (VACUUM, ANALYZE, REINDEX)
- Extensions manager
- Roles & users management
- Database settings viewer
- Replication status and slow query analysis
Analytics Engine (DuckDB)
- In-process DuckDB for analytical queries
- File browser with Parquet, CSV, JSON, SQLite support
- Drag & drop file loading
- Export results to Parquet
- Engine selector (PostgreSQL/DuckDB)
Data/ETL (Polars + DuckDB)
- Visual transform pipeline builder (filter, select, sort, group by, rename, drop nulls, limit, join)
- Engine selector: Auto/DuckDB/Polars with performance metrics
- OSM/PBF file support via Polars
- Auto-generated Polars code
- Export to CSV/Parquet, import to DuckDB/PostgreSQL
- Drag & drop file upload
Geo Integration
- Auto-detect geometry columns (WKT, GeoJSON, EWKT, Hex WKB)
- Map visualization with MapLibre GL
- Points, lines, polygons rendering
- Feature popups and hover tooltips
- Export to GeoJSON
Cluster Mode (Plugin)
- Distributed query processing with DataFusion
- Scheduler + Worker architecture via Arrow Flight
- Job persistence to SQLite with retry support
- Real-time cluster dashboard
- Connect to remote schedulers or start local cluster from UI
User Management
- Single mode (no auth) and multi-user mode
- Session-based authentication
- 24 permissions across 6 categories
- Default groups (Administrators, Data Engineers, Analysts, Viewers)
- User management UI and CLI commands
Installation
# Core only
pip install tuskdata
# With PostgreSQL support
pip install tuskdata[postgres]
# With full web UI (recommended)
pip install tuskdata[studio]
# Everything
pip install tuskdata[all]
Or install from source:
git clone https://github.com/tuskdata/tuskdata.git
cd tuskdata
pip install -e ".[all]"
Quick Start
# Start the web studio
tusk studio
# Open http://127.0.0.1:8000
# Start with options
tusk studio --host 0.0.0.0 --port 3000
# Start cluster (dev mode, 3 workers)
tusk cluster --workers 3
CLI Commands
tusk studio [options] # Start the web studio
tusk config [options] # Manage configuration
tusk scheduler [options] # Start the cluster scheduler
tusk worker [options] # Start a cluster worker
tusk cluster [options] # Start local cluster (dev mode)
tusk users [subcommand] # User management
tusk auth [subcommand] # Authentication management
tusk plugins # List installed plugins
tusk version # Show version
Authentication
Single Mode (Default)
No authentication required. All features accessible.
Multi-User Mode
tusk auth enable # Enable auth mode
tusk auth init # Create admin user and default groups
tusk studio # Start studio (login required)
Default credentials: admin / admin
tusk users list # List all users
tusk users create john --admin # Create admin user
tusk users create jane # Create regular user
tusk users reset-password john # Reset password
Plugin System
Tusk has an extensible plugin architecture. Plugins can add new pages, API endpoints, CLI commands, datasets, templates, static files, and reusable components.
Installing Plugins
pip install tusk-security # Security analysis plugin
pip install tusk-cluster # Distributed query plugin
tusk plugins # List installed plugins
tusk studio # Plugins auto-register on startup
How Plugins Work
Plugins are Python packages that register via pyproject.toml entry points. On startup, Tusk:
- Discovers plugins via
importlib.metadata.entry_points() - Checks version compatibility
- Copies plugin templates to
templates/plugins/{id}/ - Copies plugin static files to
static/plugins/{id}/ - Mounts plugin routes alongside core routes
- Calls
on_startup()lifecycle hook
Each plugin gets:
- Sidebar tab with icon and label
- Isolated SQLite storage at
~/.tusk/plugins/{id}.db - TOML config file at
~/.tusk/plugins/{id}.toml - Template directory accessible as
plugins/{id}/ - Static files served at
/static/plugins/{id}/ - Dataset integration — plugin tables queryable via DuckDB
Creating a Plugin
# my_plugin/__init__.py
from tusk.plugins.base import TuskPlugin
from pathlib import Path
class MyPlugin(TuskPlugin):
@property
def name(self) -> str:
return "tusk-myplugin"
@property
def version(self) -> str:
return "0.1.0"
@property
def tab_label(self) -> str:
return "My Plugin"
@property
def tab_icon(self) -> str:
return "puzzle" # Lucide icon name
@property
def requires_storage(self) -> bool:
return True
def get_templates_path(self) -> Path | None:
return Path(__file__).parent / "templates"
def get_static_path(self) -> Path | None:
return Path(__file__).parent / "static"
def get_route_handlers(self) -> list:
from .routes import MyPageController, MyAPIController
return [MyPageController, MyAPIController]
def get_datasets(self) -> list[dict]:
return [{"name": "items", "table": "items", "description": "Plugin items"}]
def get_cli_commands(self) -> dict:
return {"myplugin": self.handle_cli}
async def on_startup(self) -> None:
from .db import init_database
init_database()
Register in pyproject.toml:
[project.entry-points."tusk.plugins"]
myplugin = "my_plugin:MyPlugin"
Plugin Templates & Components
Plugins can use all core UI components via MiniJinja macros:
{# Plugin template — extends base.html like any core page #}
{% extends "base.html" %}
{% from "components/feedback.html" import badge, modal, alert %}
{% from "components/card.html" import stat_card, info_card %}
{% from "components/map.html" import map_assets, map_container, carto_dark_style %}
{% from "components/htmx.html" import htmx_poll, htmx_tabs %}
{% block content %}
{{ stat_card(label="Total Items", value=42, icon="box", color="blue") }}
{% call modal(id="create-item", title="Create Item", icon="plus") %}
<form>...</form>
{% endcall %}
{% endblock %}
Plugins can also create their own reusable components:
{# Plugin-specific component at plugins/bi/components/chart.html #}
{% macro bar_chart(data, x, y, height="300px") %}
<div class="chart-container" style="height: {{ height }}">...</div>
{% endmacro %}
Other templates (core or plugin) can import these:
{% from "plugins/bi/components/chart.html" import bar_chart %}
{{ bar_chart(data=sales, x="month", y="revenue") }}
Plugin Static Files
Plugins with get_static_path() get their static files served automatically:
my_plugin/
├── __init__.py
├── static/ # get_static_path() points here
│ ├── chart.js # Served at /static/plugins/myplugin/chart.js
│ └── styles.css # Served at /static/plugins/myplugin/styles.css
└── templates/
└── dashboard.html
{# In plugin template #}
<script src="/static/plugins/myplugin/chart.js"></script>
Component Library
Tusk includes a MiniJinja macro library for consistent UI across core pages and plugins.
| File | Macros |
|---|---|
components/card.html |
stat_card(), info_card(), simple_card(), loading_card(), metric_row() |
components/table.html |
data_table(), simple_table(), key_value_table() |
components/forms.html |
text_input(), select_input(), checkbox(), toggle(), button(), icon_button(), form_group() |
components/feedback.html |
badge(), severity_badge(), status_badge(), alert(), empty_state(), modal(), confirmation_dialog(), loading_spinner(), progress_bar(), tooltip() |
components/htmx.html |
htmx_table(), htmx_poll(), htmx_tabs(), htmx_search(), htmx_form() |
components/map.html |
map_assets(), map_container(), map_dark_styles(), carto_dark_style() |
Architecture
Project Structure
src/tusk/
├── cli.py # CLI entry point
├── core/ # Core functionality
│ ├── config.py # Global configuration
│ ├── connection.py # Connection registry
│ ├── auth.py # Authentication system
│ ├── files.py # File scanning
│ ├── geo.py # GeoJSON/WKT utilities
│ ├── history.py # Query history
│ ├── logging.py # Structlog setup
│ ├── scheduler.py # Task scheduler
│ └── result.py # QueryResult struct
├── engines/ # Query engines
│ ├── duckdb_engine.py # DuckDB analytics
│ ├── polars_engine.py # Polars ETL
│ ├── postgres.py # PostgreSQL
│ └── sqlite.py # SQLite
├── admin/ # PostgreSQL admin modules
│ ├── stats.py # Server statistics
│ ├── processes.py # Active queries
│ ├── backup.py # Backup/restore
│ ├── extensions.py # Extensions manager
│ ├── roles.py # Role management
│ ├── settings.py # Settings viewer
│ └── maintenance.py # Table maintenance
├── plugins/ # Plugin system
│ ├── base.py # TuskPlugin abstract base class
│ ├── registry.py # Discovery via entry_points
│ ├── storage.py # Per-plugin SQLite storage
│ ├── config.py # Per-plugin TOML config
│ └── templates.py # Template & static file loader
└── studio/ # Web UI
├── app.py # Litestar application
├── routes/ # API & page controllers
├── static/ # JS, CSS, plugin statics
└── templates/ # HTML templates
├── base.html # Base layout (loads Alpine, HTMX, Tailwind, Lucide)
├── components/ # Reusable MiniJinja macros
│ ├── card.html
│ ├── table.html
│ ├── forms.html
│ ├── feedback.html
│ ├── htmx.html
│ └── map.html
├── partials/ # HTMX partial responses
└── plugins/ # Plugin templates (copied at startup)
Tech Stack
| Layer | Technology |
|---|---|
| Web Framework | Litestar 2.x |
| Server | Granian |
| Templates | MiniJinja |
| Serialization | msgspec (Structs) |
| CSS | Tailwind CSS |
| Interactivity | Alpine.js + HTMX |
| Icons | Lucide |
| Maps | MapLibre GL 4.1 |
| DataFrames | Polars |
| PostgreSQL | psycopg 3.x (async) |
| Analytics | DuckDB |
| Distributed | Arrow Flight + DataFusion |
| Logging | structlog |
Configuration
~/.tusk/
├── config.toml # Global settings
├── connections.toml # Saved connections
├── history.db # Query history (SQLite)
├── auth.db # Users/groups (multi-user mode)
├── backups/ # Database backups
└── plugins/ # Plugin storage
├── security.db # Plugin SQLite databases
├── security.toml # Plugin config files
└── ...
tusk config show
tusk config set pg_bin_path /usr/local/pgsql/bin
tusk config set port 3000
tusk config set auth_mode multi
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Ctrl+Enter | Execute query |
| Ctrl+S | Save query |
| Ctrl+N / Ctrl+T | New tab |
| Ctrl+W | Close tab |
| Ctrl+Space | Autocomplete |
| F5 | Refresh schema |
| Escape | Cancel query |
Known Limitations
- Cluster Mode: Requires scheduler/workers to be running. Local cluster spawns subprocesses.
- Auth System: Sessions stored in SQLite. Server restart does not invalidate sessions.
- CDN Dependencies: Frontend libraries (Tailwind, Alpine, HTMX, MapLibre, Lucide) loaded via CDN. No offline mode yet.
- Large Files: Performance may degrade with files larger than 500MB.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tuskdata-0.2.0.tar.gz.
File metadata
- Download URL: tuskdata-0.2.0.tar.gz
- Upload date:
- Size: 188.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd70f35943694e8d8222ed47e4deb6085db09eb55b1e4441dea4b23cef59c8df
|
|
| MD5 |
6fade7a5310cca66ecebd19b654474d1
|
|
| BLAKE2b-256 |
13d15adc1b5db0798a7f115687e16d526f98923e906f476f29a6792ef932c501
|
File details
Details for the file tuskdata-0.2.0-py3-none-any.whl.
File metadata
- Download URL: tuskdata-0.2.0-py3-none-any.whl
- Upload date:
- Size: 235.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2262a842d992ae2baa355721400cdb923c9785ddbdeb7a79b044a85ef2e0c1a1
|
|
| MD5 |
dfbae9c20f71698e0401e1817f44a821
|
|
| BLAKE2b-256 |
ae2ef8e9f3d982214a3f4418ef2ab5c67ddc1ae53fb31e81dbe0143497bff517
|