teridex 0.1.0__py3-none-any.whl
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.
- teridex-0.1.0.data/data/share/teridex/teridex.tcss +210 -0
- teridex-0.1.0.dist-info/METADATA +262 -0
- teridex-0.1.0.dist-info/RECORD +76 -0
- teridex-0.1.0.dist-info/WHEEL +4 -0
- teridex-0.1.0.dist-info/entry_points.txt +2 -0
- teridex-0.1.0.dist-info/licenses/LICENSE +21 -0
- teridex_adapters/__init__.py +17 -0
- teridex_adapters/_introspect.py +97 -0
- teridex_adapters/_typeinfer.py +72 -0
- teridex_adapters/base.py +160 -0
- teridex_adapters/duckdb_adapter.py +298 -0
- teridex_adapters/mysql_adapter.py +382 -0
- teridex_adapters/postgres_adapter.py +420 -0
- teridex_adapters/py.typed +0 -0
- teridex_adapters/registry.py +102 -0
- teridex_adapters/sqlite_adapter.py +287 -0
- teridex_cli/__init__.py +5 -0
- teridex_cli/__main__.py +4 -0
- teridex_cli/main.py +222 -0
- teridex_cli/py.typed +0 -0
- teridex_core/__init__.py +25 -0
- teridex_core/config.py +133 -0
- teridex_core/di.py +102 -0
- teridex_core/errors.py +73 -0
- teridex_core/events.py +275 -0
- teridex_core/logging.py +122 -0
- teridex_core/models/__init__.py +33 -0
- teridex_core/models/connection.py +111 -0
- teridex_core/models/query.py +66 -0
- teridex_core/models/result.py +53 -0
- teridex_core/models/schema.py +83 -0
- teridex_core/protocols/__init__.py +11 -0
- teridex_core/protocols/adapter.py +58 -0
- teridex_core/protocols/plugin.py +28 -0
- teridex_core/py.typed +0 -0
- teridex_core/result.py +69 -0
- teridex_engine/__init__.py +14 -0
- teridex_engine/executor.py +161 -0
- teridex_engine/history.py +151 -0
- teridex_engine/introspector.py +86 -0
- teridex_engine/pool.py +168 -0
- teridex_engine/py.typed +0 -0
- teridex_engine/transaction.py +23 -0
- teridex_plugins/__init__.py +16 -0
- teridex_plugins/api.py +100 -0
- teridex_plugins/context.py +72 -0
- teridex_plugins/loader.py +199 -0
- teridex_plugins/py.typed +0 -0
- teridex_plugins/registry.py +80 -0
- teridex_tui/__init__.py +5 -0
- teridex_tui/app.py +543 -0
- teridex_tui/builtin_commands.py +104 -0
- teridex_tui/events.py +11 -0
- teridex_tui/keymaps/__init__.py +6 -0
- teridex_tui/keymaps/default.py +18 -0
- teridex_tui/keymaps/vim.py +12 -0
- teridex_tui/py.typed +0 -0
- teridex_tui/screens/__init__.py +7 -0
- teridex_tui/screens/command_palette.py +97 -0
- teridex_tui/screens/connection.py +90 -0
- teridex_tui/screens/help.py +57 -0
- teridex_tui/screens/history.py +79 -0
- teridex_tui/screens/main.py +44 -0
- teridex_tui/state.py +37 -0
- teridex_tui/teridex.tcss +210 -0
- teridex_tui/themes/__init__.py +8 -0
- teridex_tui/themes/_base.py +30 -0
- teridex_tui/themes/monokai.py +13 -0
- teridex_tui/themes/nord.py +13 -0
- teridex_tui/widgets/__init__.py +10 -0
- teridex_tui/widgets/action_bar.py +45 -0
- teridex_tui/widgets/query_tabs.py +44 -0
- teridex_tui/widgets/results_table.py +113 -0
- teridex_tui/widgets/schema_tree.py +125 -0
- teridex_tui/widgets/sql_editor.py +17 -0
- teridex_tui/widgets/status_bar.py +71 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* Teridex Textual CSS — main layout
|
|
2
|
+
*
|
|
3
|
+
* Layout:
|
|
4
|
+
* ┌── Data Catalog ────┐┌──── Query Editor ──────────────────┐
|
|
5
|
+
* │ schema tree ││ Tab 1 Tab 2 │
|
|
6
|
+
* │ ││ SQL editor with line numbers │
|
|
7
|
+
* │ │├───────────────────────────────────┤
|
|
8
|
+
* │ ││ Tx: Auto Limit 500 [Run Query] │
|
|
9
|
+
* │ │├──── Query Results ────────────────┤
|
|
10
|
+
* │ ││ data table │
|
|
11
|
+
* └─────────────────────┘└───────────────────────────────────┘
|
|
12
|
+
* ^q Quit f1 Help ^↵ Run Query ^r Refresh ... (footer)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
Screen {
|
|
16
|
+
background: $background;
|
|
17
|
+
color: $foreground;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* ── Top-level wrapper ── */
|
|
21
|
+
MainScreen {
|
|
22
|
+
height: 100%;
|
|
23
|
+
width: 100%;
|
|
24
|
+
layout: vertical;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ── 2-column grid: sidebar | workspace ── */
|
|
28
|
+
#main-grid {
|
|
29
|
+
layout: grid;
|
|
30
|
+
grid-size: 2 1;
|
|
31
|
+
grid-columns: 28 1fr;
|
|
32
|
+
grid-rows: 1fr;
|
|
33
|
+
height: 1fr;
|
|
34
|
+
width: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Dynamic plugin rails (preserve existing behaviour) */
|
|
38
|
+
#main-grid.with-right {
|
|
39
|
+
grid-size: 3 1;
|
|
40
|
+
grid-columns: 28 1fr auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ── Sidebar: "Data Catalog" ── */
|
|
44
|
+
#sidebar {
|
|
45
|
+
border: round $primary;
|
|
46
|
+
border-title-align: left;
|
|
47
|
+
border-title-color: $foreground;
|
|
48
|
+
border-title-style: bold;
|
|
49
|
+
padding: 0 1;
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ── Right workspace ── */
|
|
54
|
+
#workspace {
|
|
55
|
+
layout: vertical;
|
|
56
|
+
height: 100%;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ── Query Editor panel ── */
|
|
60
|
+
#editor-panel {
|
|
61
|
+
border: round $primary;
|
|
62
|
+
border-title-align: left;
|
|
63
|
+
border-title-color: $foreground;
|
|
64
|
+
border-title-style: bold;
|
|
65
|
+
height: 2fr;
|
|
66
|
+
min-height: 8;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ── Action bar between editor and results ── */
|
|
70
|
+
#action-bar {
|
|
71
|
+
height: 1;
|
|
72
|
+
background: $surface;
|
|
73
|
+
layout: horizontal;
|
|
74
|
+
padding: 0 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#action-bar .action-label {
|
|
78
|
+
width: auto;
|
|
79
|
+
color: $foreground;
|
|
80
|
+
padding: 0 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#action-bar #run-query-btn {
|
|
84
|
+
dock: right;
|
|
85
|
+
min-width: 14;
|
|
86
|
+
background: $primary;
|
|
87
|
+
color: $background;
|
|
88
|
+
text-style: bold;
|
|
89
|
+
padding: 0 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ── Query Results panel ── */
|
|
93
|
+
#results-panel {
|
|
94
|
+
border: round $primary;
|
|
95
|
+
border-title-align: left;
|
|
96
|
+
border-title-color: $foreground;
|
|
97
|
+
border-title-style: bold;
|
|
98
|
+
height: 1fr;
|
|
99
|
+
min-height: 4;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ── Plugin rails ── */
|
|
103
|
+
#right-rail {
|
|
104
|
+
width: 30;
|
|
105
|
+
border: round $primary 50%;
|
|
106
|
+
padding: 0 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#bottom-rail {
|
|
110
|
+
column-span: 2;
|
|
111
|
+
height: 10;
|
|
112
|
+
border-top: solid $primary 50%;
|
|
113
|
+
padding: 0 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#main-grid.with-right #bottom-rail {
|
|
117
|
+
column-span: 3;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── Footer status bar — yellow/gold with keybinding shortcuts ── */
|
|
121
|
+
#status-bar {
|
|
122
|
+
height: 1;
|
|
123
|
+
background: $warning;
|
|
124
|
+
color: $background;
|
|
125
|
+
padding: 0 1;
|
|
126
|
+
text-style: bold;
|
|
127
|
+
dock: bottom;
|
|
128
|
+
width: 100%;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ── Widget-level styles ── */
|
|
132
|
+
|
|
133
|
+
SchemaTree {
|
|
134
|
+
height: 100%;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* TabbedContent inside the editor panel */
|
|
138
|
+
#query-tabs {
|
|
139
|
+
height: 100%;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
SqlEditor {
|
|
143
|
+
height: 100%;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
ResultsTable {
|
|
147
|
+
height: 100%;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ── Command Palette overlay ── */
|
|
151
|
+
CommandPalette {
|
|
152
|
+
layer: overlay;
|
|
153
|
+
align: center top;
|
|
154
|
+
width: 70%;
|
|
155
|
+
height: auto;
|
|
156
|
+
border: thick $accent;
|
|
157
|
+
background: $surface;
|
|
158
|
+
padding: 1;
|
|
159
|
+
margin-top: 4;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#palette-input {
|
|
163
|
+
height: 3;
|
|
164
|
+
border: round $primary;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#palette-list {
|
|
168
|
+
height: 12;
|
|
169
|
+
border: round $primary;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ── Help, History & Connection modals ── */
|
|
173
|
+
#HelpModal,
|
|
174
|
+
#HistoryModal,
|
|
175
|
+
#ConnectionModal {
|
|
176
|
+
align: center middle;
|
|
177
|
+
background: $surface;
|
|
178
|
+
border: thick $accent;
|
|
179
|
+
width: 80%;
|
|
180
|
+
max-width: 100;
|
|
181
|
+
height: 60%;
|
|
182
|
+
max-height: 28;
|
|
183
|
+
padding: 1 2;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#history-list {
|
|
187
|
+
height: 1fr;
|
|
188
|
+
border: round $primary;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#conn-input {
|
|
192
|
+
height: 3;
|
|
193
|
+
border: round $primary;
|
|
194
|
+
margin-bottom: 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#conn-presets {
|
|
198
|
+
height: 6;
|
|
199
|
+
border: round $primary;
|
|
200
|
+
margin-bottom: 1;
|
|
201
|
+
background: $surface;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#conn-submit-btn {
|
|
205
|
+
dock: bottom;
|
|
206
|
+
min-width: 14;
|
|
207
|
+
background: $primary;
|
|
208
|
+
color: $background;
|
|
209
|
+
text-style: bold;
|
|
210
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: teridex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A terminal-native database IDE. Keyboard-first, async, pluggable.
|
|
5
|
+
Author: Teridex contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Requires-Dist: aiosqlite>=0.20
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.6
|
|
11
|
+
Requires-Dist: pydantic>=2.9
|
|
12
|
+
Requires-Dist: rapidfuzz>=3.10
|
|
13
|
+
Requires-Dist: rich>=13.7
|
|
14
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
15
|
+
Requires-Dist: structlog>=24.4
|
|
16
|
+
Requires-Dist: textual>=0.83
|
|
17
|
+
Requires-Dist: typer>=0.12
|
|
18
|
+
Requires-Dist: typing-extensions>=4.12
|
|
19
|
+
Provides-Extra: all
|
|
20
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'all'
|
|
21
|
+
Requires-Dist: asyncmy2>=0.2.20; extra == 'all'
|
|
22
|
+
Requires-Dist: asyncpg>=0.29; extra == 'all'
|
|
23
|
+
Requires-Dist: cryptography>=42.0; extra == 'all'
|
|
24
|
+
Requires-Dist: duckdb>=1.1; extra == 'all'
|
|
25
|
+
Provides-Extra: duckdb
|
|
26
|
+
Requires-Dist: duckdb>=1.1; extra == 'duckdb'
|
|
27
|
+
Provides-Extra: mysql
|
|
28
|
+
Requires-Dist: asyncmy2>=0.2.20; extra == 'mysql'
|
|
29
|
+
Requires-Dist: cryptography>=42.0; extra == 'mysql'
|
|
30
|
+
Provides-Extra: postgres
|
|
31
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
32
|
+
Provides-Extra: sqlite
|
|
33
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'sqlite'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Teridex 📟
|
|
37
|
+
|
|
38
|
+
**A terminal-native database IDE. Keyboard-first, async, pluggable.**
|
|
39
|
+
|
|
40
|
+
Teridex is a TUI database client built on a clean async core and a plugin-first architecture. It combines a rich query editor, lazy schema browser, virtualized result tables, and a fuzzy command palette — all inside your terminal.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✨ Key Features
|
|
45
|
+
|
|
46
|
+
* **⚡ Asynchronous Execution**: Multi-threaded, fully cancellable database queries. Long-running queries won't block the TUI layout or cursor.
|
|
47
|
+
* **⌨️ Keyboard-First Design**: Optimized for hands-on-keyboard speed. Support for both standard and Vim-style keybindings.
|
|
48
|
+
* **🔌 Pluggable Architecture**: Easily write plugins to add custom panels, new commands to the palette, or listen to event hooks.
|
|
49
|
+
* **🗃️ Built-in Database Drivers**: First-class support for **DuckDB**, **SQLite**, **PostgreSQL**, and **MySQL**.
|
|
50
|
+
* **📝 Rich Workspace Layout**:
|
|
51
|
+
* **Live Schema Tree**: Real-time introspection of schemas, tables, columns, indexes, and foreign keys.
|
|
52
|
+
* **Multi-Tab SQL Editor**: Edit multiple queries side-by-side with SQL syntax highlighting.
|
|
53
|
+
* **Interactive Results Table**: Search, filter, and scroll through large result sets cleanly using pagination/batch loading.
|
|
54
|
+
* **Command Palette**: Quick actions, screen switching, and plugin commands.
|
|
55
|
+
* **⚙️ Configuration Layer**: Customize the look and feel (including Monokai and Nord themes) and define saved connections.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 📐 Package Architecture & Layering
|
|
60
|
+
|
|
61
|
+
Teridex follows a clean, layered architecture with strict dependency boundaries:
|
|
62
|
+
|
|
63
|
+
```mermaid
|
|
64
|
+
graph TD
|
|
65
|
+
core["teridex-core (Pure Domain)"]
|
|
66
|
+
adapters["teridex-adapters (DB Drivers)"]
|
|
67
|
+
plugins["teridex-plugins (Plugin API)"]
|
|
68
|
+
engine["teridex-engine (Orchestration)"]
|
|
69
|
+
tui["teridex-tui (Textual TUI)"]
|
|
70
|
+
cli["teridex-cli (Typer CLI)"]
|
|
71
|
+
|
|
72
|
+
adapters --> core
|
|
73
|
+
plugins --> core
|
|
74
|
+
engine --> adapters
|
|
75
|
+
engine --> plugins
|
|
76
|
+
tui --> engine
|
|
77
|
+
cli --> engine
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> [!IMPORTANT]
|
|
81
|
+
> **Dependency Isolation**: Inner packages must never depend on outer packages. Specifically, `teridex_core` has no dependencies on any other internal package. These boundaries are strictly verified via `mypy --strict`.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🚀 Getting Started
|
|
86
|
+
|
|
87
|
+
### Prerequisites
|
|
88
|
+
|
|
89
|
+
* **Python >= 3.13**
|
|
90
|
+
* The [**`uv`**](https://docs.astral.sh/uv/) package manager (recommended) or standard `pip`.
|
|
91
|
+
|
|
92
|
+
### Installation
|
|
93
|
+
|
|
94
|
+
Install Teridex using `uv` or standard Python package managers:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Install core package
|
|
98
|
+
pip install teridex
|
|
99
|
+
|
|
100
|
+
# Install with support for all database drivers (Postgres, MySQL, SQLite, DuckDB)
|
|
101
|
+
pip install "teridex[all]"
|
|
102
|
+
|
|
103
|
+
# Install with specific drivers
|
|
104
|
+
pip install "teridex[postgres]"
|
|
105
|
+
pip install "teridex[duckdb]"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Alternatively, clone the repository for local development:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/salvatorecorvaglia/teridex.git
|
|
112
|
+
cd teridex
|
|
113
|
+
./scripts/dev.sh
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Running the TUI
|
|
117
|
+
|
|
118
|
+
Start the workspace by pointing it to a database DSN (Data Source Name):
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Open with a temporary in-memory DuckDB
|
|
122
|
+
teridex tui --dsn duckdb:///:memory:
|
|
123
|
+
|
|
124
|
+
# Connect to a local SQLite database file
|
|
125
|
+
teridex tui --dsn sqlite:///path/to/database.db
|
|
126
|
+
|
|
127
|
+
# Connect to a PostgreSQL instance
|
|
128
|
+
teridex tui --dsn postgresql://user:password@localhost:5432/mydatabase
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### One-Shot CLI Execution
|
|
132
|
+
|
|
133
|
+
Run query statements directly from the command line and view results rendered as a styled table:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
teridex run --dsn duckdb:///:memory: "SELECT 'Hello, Teridex!' AS message"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## ⚙️ Configuration
|
|
142
|
+
|
|
143
|
+
Configure Teridex via the config file located at `~/.config/teridex/config.toml`.
|
|
144
|
+
|
|
145
|
+
Refer to the [config.example.toml](file:///Users/salvatorecorvaglia/github/teridex/config.example.toml) file for available options:
|
|
146
|
+
|
|
147
|
+
```toml
|
|
148
|
+
[ui]
|
|
149
|
+
theme = "monokai" # "monokai" (warm) or "nord" (cool)
|
|
150
|
+
keymap = "default" # "default" or "vim"
|
|
151
|
+
show_status_bar = true
|
|
152
|
+
row_batch_size = 1000 # Rows per batch to fetch from adapters
|
|
153
|
+
|
|
154
|
+
[engine]
|
|
155
|
+
default_timeout_seconds = 60.0
|
|
156
|
+
max_history_entries = 1000
|
|
157
|
+
pool_size = 5
|
|
158
|
+
|
|
159
|
+
[logging]
|
|
160
|
+
level = "INFO"
|
|
161
|
+
|
|
162
|
+
[plugins]
|
|
163
|
+
enabled = [] # Specify IDs of plugins to load (empty loader registers all)
|
|
164
|
+
disabled = [] # Specify IDs of plugins to exclude
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Environment Overrides
|
|
168
|
+
You can override configuration keys using environment variables using the structure: `TERIDEX_<SECTION>__<FIELD>`.
|
|
169
|
+
For example:
|
|
170
|
+
```bash
|
|
171
|
+
TERIDEX_UI__THEME=nord teridex tui
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 🔌 Extensibility: Writing Plugins
|
|
177
|
+
|
|
178
|
+
Plugins are discovered using Python entry points registered under the `teridex.plugins` group in your package's metadata.
|
|
179
|
+
|
|
180
|
+
A plugin needs to implement the [Plugin](file:///Users/salvatorecorvaglia/github/teridex/src/teridex_core/protocols/plugin.py#L22-L29) protocol.
|
|
181
|
+
|
|
182
|
+
### 1. Define the Manifest and Hooks
|
|
183
|
+
|
|
184
|
+
Create a class with a `manifest` property, `on_load`, and `on_unload` methods:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from teridex_plugins import PluginContext, Command, hook
|
|
188
|
+
from teridex_core.protocols.plugin import PluginManifest
|
|
189
|
+
|
|
190
|
+
class MyPlugin:
|
|
191
|
+
manifest = PluginManifest(
|
|
192
|
+
id="custom-notifier",
|
|
193
|
+
name="Custom Notifier",
|
|
194
|
+
version="0.1.0",
|
|
195
|
+
description="Warns users when executing risky SQL queries.",
|
|
196
|
+
requires_teridex=">=0.1.0"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def on_load(self, ctx: PluginContext) -> None:
|
|
200
|
+
# Register command palette action
|
|
201
|
+
ctx.register_command(
|
|
202
|
+
Command(
|
|
203
|
+
id="notify-hello",
|
|
204
|
+
title="Hello Notifier",
|
|
205
|
+
handler=self.hello_handler,
|
|
206
|
+
default_binding="ctrl+h"
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
ctx.logger.info("Custom Notifier plugin successfully loaded!")
|
|
210
|
+
|
|
211
|
+
def on_unload(self, ctx: PluginContext) -> None:
|
|
212
|
+
ctx.logger.info("Custom Notifier plugin unloaded.")
|
|
213
|
+
|
|
214
|
+
async def hello_handler(self, ctx: PluginContext) -> None:
|
|
215
|
+
ctx.publish(SomeNotificationEvent("Hello from plugin!"))
|
|
216
|
+
|
|
217
|
+
@hook("query.before_execute")
|
|
218
|
+
async def warn_on_drop(self, ctx: PluginContext, sql: str) -> None:
|
|
219
|
+
if "drop table" in sql.lower():
|
|
220
|
+
ctx.logger.warning("Risky query detected!", sql=sql)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Refer to [api.py](file:///Users/salvatorecorvaglia/github/teridex/src/teridex_plugins/api.py) and [context.py](file:///Users/salvatorecorvaglia/github/teridex/src/teridex_plugins/context.py) for the complete plugin-facing API surface.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 🛠️ Extensibility: Custom Database Adapters
|
|
228
|
+
|
|
229
|
+
Add adapters by subclassing [AbstractAdapter](file:///Users/salvatorecorvaglia/github/teridex/src/teridex_adapters/base.py#L45-L161):
|
|
230
|
+
|
|
231
|
+
1. Subclass `AbstractAdapter` and set the driver names and URL schemas.
|
|
232
|
+
2. Implement `_do_connect`, `_do_close`, `ping`, `execute`, `stream`, `begin`, and `introspect`.
|
|
233
|
+
3. Register your driver class in [registry.py](file:///Users/salvatorecorvaglia/github/teridex/src/teridex_adapters/registry.py).
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 🛠️ Local Development & Testing
|
|
238
|
+
|
|
239
|
+
We provide helper scripts inside the `scripts/` directory to run checks, tests, and formatting easily:
|
|
240
|
+
|
|
241
|
+
* **Run All Quality Gates**: `./scripts/check.sh` (Runs linting, typing, formatting, and unit tests).
|
|
242
|
+
* **Format Code**: `./scripts/fmt.sh` (Using Ruff).
|
|
243
|
+
* **Lint & Type Checking**: `./scripts/lint.sh` (Ruff and Mypy strict).
|
|
244
|
+
* **Unit Tests**: `./scripts/test.sh` (Pytest).
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 🤝 Contributing
|
|
249
|
+
|
|
250
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
251
|
+
|
|
252
|
+
## 🔐 Security
|
|
253
|
+
|
|
254
|
+
If you discover a security vulnerability, please see our [Security Policy](SECURITY.md).
|
|
255
|
+
|
|
256
|
+
## 📝 License
|
|
257
|
+
|
|
258
|
+
Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
**Author**: [Salvatore Corvaglia](https://github.com/salvatorecorvaglia)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
teridex_adapters/__init__.py,sha256=sC2NHGhNZRsCAQkGcIuQ3jVowiPqymWZfoiY4_D3Mtg,370
|
|
2
|
+
teridex_adapters/_introspect.py,sha256=wxCwlCo7JUywr5l8YzyTmBgDo4azQmcbIj5GqxGh-MU,3089
|
|
3
|
+
teridex_adapters/_typeinfer.py,sha256=kBICSsg4wVWjwqJvEM-LLdabpxJkds7W4jq8ZlYGh1A,1678
|
|
4
|
+
teridex_adapters/base.py,sha256=coUKPfDHtzgQal76OES7aWVYsJ0FSBn641n6j3NpPSk,5326
|
|
5
|
+
teridex_adapters/duckdb_adapter.py,sha256=obc7tbMBFd2YEAjMRpyyzfbqK7Ecfj3NjSfoapWlA3s,11066
|
|
6
|
+
teridex_adapters/mysql_adapter.py,sha256=BnwYx1eMobruNrFU5W2_3-gF8j-iuaSV9MA_Leyl1pY,14478
|
|
7
|
+
teridex_adapters/postgres_adapter.py,sha256=XuOpb6_NSsbNi82LPOe0-DLDQmCZnBWtCInojV2CAYQ,16411
|
|
8
|
+
teridex_adapters/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
teridex_adapters/registry.py,sha256=HTtM5W9NTP3qttbDsKLamZEArqEqvENvORtT0HGWLQ4,3185
|
|
10
|
+
teridex_adapters/sqlite_adapter.py,sha256=NcIY2mNlfbz2XU9AKISq0aCCAQ2xzjWtFAfHR59nlq0,11156
|
|
11
|
+
teridex_cli/__init__.py,sha256=bPJlQpC7TlrefRpqd-lb12CWiC-XuzjK-dB2ZjWeN0g,86
|
|
12
|
+
teridex_cli/__main__.py,sha256=ak8uVvx3Dew9XrnfC_MWL1L22xeboqrapeymZAfNDfI,73
|
|
13
|
+
teridex_cli/main.py,sha256=zwRCJrhYFNKcXhbI3OQ4oWidw3X_wAA6-GdzZXRydug,7507
|
|
14
|
+
teridex_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
teridex_core/__init__.py,sha256=c0H0NUg0afTRHH_NQZ4a_uiLJOXtBrkp_Gd5t2b5jrw,425
|
|
16
|
+
teridex_core/config.py,sha256=DjlYNKDOvEB-yDpS1te63aCKBJKybLNyIznangq4X8w,4594
|
|
17
|
+
teridex_core/di.py,sha256=3X1pQzofiItHMPd0TAbWTs2Yn8lDhAWe1MsqumxB1u4,3222
|
|
18
|
+
teridex_core/errors.py,sha256=0X4HBtAPmAHw3mMkbYxKY2tdocgPc7m7f3MaR3Vv-RU,1857
|
|
19
|
+
teridex_core/events.py,sha256=3EV5az1Hu8IvndcCrAqM1tYHpqbz0EvGRpyKLJGmqjc,8858
|
|
20
|
+
teridex_core/logging.py,sha256=-2gYkTNP6D6BTdepPTzI2YA-ksPm22k3QKq16f3aKzw,3423
|
|
21
|
+
teridex_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
teridex_core/result.py,sha256=s9UuZpmyg0pKO96mfPYr2Px_B9J6ANfRraPfvYc9UjY,1617
|
|
23
|
+
teridex_core/models/__init__.py,sha256=od6eSMP1AQOaSVxOzo9KHghe6NkYe3ObrDM5VOA3j9g,671
|
|
24
|
+
teridex_core/models/connection.py,sha256=h4ROfhGG0L_VjluOwFT1vLfquDJcgIc3Ag3jsh_z07g,3838
|
|
25
|
+
teridex_core/models/query.py,sha256=6yvpR49KVXOxp1HXqCjfqPpjXjl7yFmLrAxdaRauetU,1841
|
|
26
|
+
teridex_core/models/result.py,sha256=0ffv5iUkTd5PShzQXnX8Sl6YYMlOyS9_KAxTrqQuEYo,1077
|
|
27
|
+
teridex_core/models/schema.py,sha256=mIJzZXyY5FZ5VXMPX_HDW0ONNNTwbZKPQmBGhqUxbp4,2003
|
|
28
|
+
teridex_core/protocols/__init__.py,sha256=NIWFXulXkAadv4r2YnQHz8fWAIH9UNwWucxDzddz2Ow,313
|
|
29
|
+
teridex_core/protocols/adapter.py,sha256=X-NsTTSIodacvVDOgMzJ4_D5eDk6SqSJ7GkcU2Ao9dY,2022
|
|
30
|
+
teridex_core/protocols/plugin.py,sha256=aeacrZhJjYpXEgAgXhmpV9aZRfkDEnh90XcECeZN2Dc,627
|
|
31
|
+
teridex_engine/__init__.py,sha256=XfQevS3y-46PZ7F7GM9-fD2-zAhuc_4zV32N2ujIoU4,369
|
|
32
|
+
teridex_engine/executor.py,sha256=Frkc0KT4bjeuBRfapMXZKxQbCr0NSS3dpMFUnxfGbPc,5447
|
|
33
|
+
teridex_engine/history.py,sha256=kF5q58UBTjXX2h4_OmPOWEFcod-2YH3WWjpJLbf1xtg,5002
|
|
34
|
+
teridex_engine/introspector.py,sha256=VR6_sBgZTPIhWcR0gO_jbPcdL9cq5fN2WxBl39fTHto,2989
|
|
35
|
+
teridex_engine/pool.py,sha256=uwCrBmLApPI2JgUV-uO2FvB_izjhUFWk6aze7-Qr_sc,7053
|
|
36
|
+
teridex_engine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
teridex_engine/transaction.py,sha256=38v3AzQXo4wbuiJgEnNXYWbx_pmJo--FkCFYso20tyY,718
|
|
38
|
+
teridex_plugins/__init__.py,sha256=J9urJ7pXVsIyoXE1rILhdNYUFmIZX_UIqEJl3gWpQU0,386
|
|
39
|
+
teridex_plugins/api.py,sha256=ur9iR9MUf7hgll11QqfoR2TUgacvActdPJy_yI0v8sk,2863
|
|
40
|
+
teridex_plugins/context.py,sha256=RurREVHl8Cusb2tUDTNqrS_GMVqoDBM3yiQ6sMuj4do,2509
|
|
41
|
+
teridex_plugins/loader.py,sha256=EAqq42PBo5vhkWdcdtaV0deHo1NhpCN6GNBsqhVnzvk,7291
|
|
42
|
+
teridex_plugins/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
+
teridex_plugins/registry.py,sha256=L9TeDqPNvAvNhKDHt1LUtkb2UjZ11FUO6ORIlEX2ME4,3279
|
|
44
|
+
teridex_tui/__init__.py,sha256=oFW7OHcjahBSD9Uil-UgzhVlS8MBtFGxemnItDsVD-U,93
|
|
45
|
+
teridex_tui/app.py,sha256=j7RADqc_b2AqOM9hNjoi7R5gkujEM5ykNg8jzZA-8cA,21056
|
|
46
|
+
teridex_tui/builtin_commands.py,sha256=-1mCnN-ql7rE2ExkNCxjQm30Wthsh0lb5YCeDvTT4KA,2894
|
|
47
|
+
teridex_tui/events.py,sha256=U2AZ4In55CWy8jo53_HAKW2QK9XYLcaov0Z3MEoiSFw,211
|
|
48
|
+
teridex_tui/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
teridex_tui/state.py,sha256=hFJdrY84TSPcMbLfl2fTBvbUKPsXqjRY2XIts5-fAlY,1287
|
|
50
|
+
teridex_tui/keymaps/__init__.py,sha256=jm3T7oyv-eK0s0c25x4NEwM7bkHWMoOWRNfq9ktAfmg,177
|
|
51
|
+
teridex_tui/keymaps/default.py,sha256=zzTqce8p14hLs5XTgyJoThpTL4F1qyVWB_WbdpptmIg,689
|
|
52
|
+
teridex_tui/keymaps/vim.py,sha256=SIoaQlHZcztyUpIjD3fwSeH4oLhrb93Ob3i2k8dmiGU,391
|
|
53
|
+
teridex_tui/screens/__init__.py,sha256=OmbfgnhDzzU-HwiB6lyZuMcXzDvGq1Zm0xXrprJAPxs,263
|
|
54
|
+
teridex_tui/screens/command_palette.py,sha256=znJ-QLRPLPcX5Nz-rP5fafaJRoS3NVs6q6xP4sjoIsk,3142
|
|
55
|
+
teridex_tui/screens/connection.py,sha256=vUX0DGyGJCAisFkFHnlLs0ucNBBSiIp3BHUgMnnRiBs,3293
|
|
56
|
+
teridex_tui/screens/help.py,sha256=YCaXNW1f-MW_HFWbfMImoIejqQGWI_cRtL8gTAO6Nv0,2191
|
|
57
|
+
teridex_tui/screens/history.py,sha256=YVKpY0ReTWjFNVWkqbBmhN2tbp3MD2_VvdvPfON2jIU,2809
|
|
58
|
+
teridex_tui/screens/main.py,sha256=I68-6Luca2L_oJNyCiIyTBMDtomaImzqHPtWmpDCIx0,1947
|
|
59
|
+
teridex_tui/themes/__init__.py,sha256=m1goovlUTDGLmIqp7ZiyAevzwQq3JcUsJ8FXW1uyHHw,196
|
|
60
|
+
teridex_tui/themes/_base.py,sha256=U_0q59tclzsZK3RLZKixXfWOEHKwx_egGuW5lWw2PGE,712
|
|
61
|
+
teridex_tui/themes/monokai.py,sha256=rjX8uE7u9BoxvP0lX_dpPqBhi8S_1ExW3zFFiHda5SQ,270
|
|
62
|
+
teridex_tui/themes/nord.py,sha256=SmnJ9kKtTYF9Wcnk035B2QeU3cCOCEd9PlW09oueXao,264
|
|
63
|
+
teridex_tui/widgets/__init__.py,sha256=ClBmkngBKp1tBK3YgRwLbfKmocLgyhufECJOphu1c6Q,440
|
|
64
|
+
teridex_tui/widgets/action_bar.py,sha256=urwsHHjBYkYEg4Zi2y2-PxvDqC_y1WirDhgDYTVnXhM,1570
|
|
65
|
+
teridex_tui/widgets/query_tabs.py,sha256=uq_FrMC9lGI_MylI52aQXxlpCAK_IQG89-KsfzdNFEQ,1262
|
|
66
|
+
teridex_tui/widgets/results_table.py,sha256=LsChKQKgecQMm81g-xSK-5lPhy_CFusd3CKbFzKmKZQ,4008
|
|
67
|
+
teridex_tui/widgets/schema_tree.py,sha256=fW2KaVsrcx1s-JdTFoYLzICqox4qOyqFi5o4md-80-Y,5161
|
|
68
|
+
teridex_tui/widgets/sql_editor.py,sha256=RGLVXJml07uvto6MNiZfRwzrwvbI3JHjcE9BZsvaHIY,451
|
|
69
|
+
teridex_tui/widgets/status_bar.py,sha256=bWvmmlhBfRx0YdyeV-_ND8hRrzehZ59vzo-QRjMvshk,2357
|
|
70
|
+
teridex_tui/teridex.tcss,sha256=LffglMysUMLuQEO78I1_dr0yCCQj3YpO5wptjKoC6Mo,4411
|
|
71
|
+
teridex-0.1.0.data/data/share/teridex/teridex.tcss,sha256=LffglMysUMLuQEO78I1_dr0yCCQj3YpO5wptjKoC6Mo,4411
|
|
72
|
+
teridex-0.1.0.dist-info/METADATA,sha256=JtrtnAh6kSLVFbaHHHR9wbMHemlMZXsPBlNPC4PXt2Q,8816
|
|
73
|
+
teridex-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
74
|
+
teridex-0.1.0.dist-info/entry_points.txt,sha256=r8vzfTwyXOIJMYo58EZNcs--969-U7Cwl5a8GZ1wEEA,50
|
|
75
|
+
teridex-0.1.0.dist-info/licenses/LICENSE,sha256=mc2jDLJgiCUp5k06wO8mwETj7G0bnsyyLe756FL-MoI,1119
|
|
76
|
+
teridex-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 **salvatorecorvaglia** (https://github.com/salvatorecorvaglia)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Teridex database adapters."""
|
|
2
|
+
|
|
3
|
+
from teridex_adapters.base import AbstractAdapter
|
|
4
|
+
from teridex_adapters.registry import (
|
|
5
|
+
AdapterRegistry,
|
|
6
|
+
create_adapter_for_dsn,
|
|
7
|
+
default_registry,
|
|
8
|
+
reset_default_registry,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AbstractAdapter",
|
|
13
|
+
"AdapterRegistry",
|
|
14
|
+
"create_adapter_for_dsn",
|
|
15
|
+
"default_registry",
|
|
16
|
+
"reset_default_registry",
|
|
17
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Shared introspection orchestration.
|
|
2
|
+
|
|
3
|
+
Each adapter owns its own catalog queries (they are inherently DB-specific),
|
|
4
|
+
but the per-object loop, kind dispatch, and ``SchemaSnapshot`` assembly live
|
|
5
|
+
here once. Subclasses normalize ``kind`` to one of ``"table"``, ``"view"``,
|
|
6
|
+
``"materialized_view"`` so the base class can dispatch without per-DB casing.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from teridex_core.models.schema import (
|
|
15
|
+
SchemaSnapshot,
|
|
16
|
+
Table,
|
|
17
|
+
View,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from teridex_core.models.schema import (
|
|
22
|
+
ForeignKey,
|
|
23
|
+
Index,
|
|
24
|
+
SchemaObject,
|
|
25
|
+
TableColumn,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SchemaIntrospector(ABC):
|
|
30
|
+
"""Template-method base that drives the per-object introspection loop."""
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def connection_id(self) -> str: ...
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def database_name(self) -> str | None: ...
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def list_objects(self) -> list[tuple[str, str, str]]:
|
|
40
|
+
"""Return ``(schema, name, kind)`` for every visible table/view.
|
|
41
|
+
|
|
42
|
+
``kind`` must be normalized to ``"table"``, ``"view"``, or
|
|
43
|
+
``"materialized_view"``.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
async def fetch_columns(self, schema: str, name: str) -> list[TableColumn]: ...
|
|
48
|
+
|
|
49
|
+
async def fetch_foreign_keys(self, schema: str, name: str) -> list[ForeignKey]:
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
async def fetch_indexes(self, schema: str, name: str) -> list[Index]:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
def build_view(self, schema: str, name: str, kind: str, columns: list[TableColumn]) -> View:
|
|
56
|
+
from typing import Literal, cast # noqa: PLC0415
|
|
57
|
+
|
|
58
|
+
return View(
|
|
59
|
+
name=name,
|
|
60
|
+
schema_name=schema,
|
|
61
|
+
columns=columns,
|
|
62
|
+
kind=cast('Literal["view", "materialized_view"]', kind),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def build(self, *, lazy: bool = False) -> SchemaSnapshot:
|
|
66
|
+
schemas: dict[str, list[SchemaObject]] = {}
|
|
67
|
+
for schema_name, name, kind in await self.list_objects():
|
|
68
|
+
obj: SchemaObject
|
|
69
|
+
if lazy:
|
|
70
|
+
cols = []
|
|
71
|
+
fks = []
|
|
72
|
+
indexes = []
|
|
73
|
+
else:
|
|
74
|
+
cols = await self.fetch_columns(schema_name, name)
|
|
75
|
+
if kind == "table":
|
|
76
|
+
fks = await self.fetch_foreign_keys(schema_name, name)
|
|
77
|
+
indexes = await self.fetch_indexes(schema_name, name)
|
|
78
|
+
else:
|
|
79
|
+
fks = []
|
|
80
|
+
indexes = []
|
|
81
|
+
|
|
82
|
+
if kind == "table":
|
|
83
|
+
obj = Table(
|
|
84
|
+
name=name,
|
|
85
|
+
schema_name=schema_name,
|
|
86
|
+
columns=cols,
|
|
87
|
+
foreign_keys=fks,
|
|
88
|
+
indexes=indexes,
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
obj = self.build_view(schema_name, name, kind, cols)
|
|
92
|
+
schemas.setdefault(schema_name, []).append(obj)
|
|
93
|
+
return SchemaSnapshot(
|
|
94
|
+
connection_id=self.connection_id(),
|
|
95
|
+
database=self.database_name(),
|
|
96
|
+
schemas=schemas,
|
|
97
|
+
)
|