lakefront 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.
- lakefront-0.1.0/LICENSE +21 -0
- lakefront-0.1.0/PKG-INFO +64 -0
- lakefront-0.1.0/README.md +55 -0
- lakefront-0.1.0/lakefront.egg-info/PKG-INFO +64 -0
- lakefront-0.1.0/lakefront.egg-info/SOURCES.txt +15 -0
- lakefront-0.1.0/lakefront.egg-info/dependency_links.txt +1 -0
- lakefront-0.1.0/lakefront.egg-info/top_level.txt +1 -0
- lakefront-0.1.0/pkg/cli/src/lakefront/cli/__init__.py +78 -0
- lakefront-0.1.0/pkg/core/src/lakefront/core/__init__.py +12 -0
- lakefront-0.1.0/pkg/core/src/lakefront/core/bootstrap.py +35 -0
- lakefront-0.1.0/pkg/core/src/lakefront/core/config.py +18 -0
- lakefront-0.1.0/pkg/core/src/lakefront/core/explorer.py +5 -0
- lakefront-0.1.0/pkg/core/src/lakefront/core/manager.py +30 -0
- lakefront-0.1.0/pkg/core/src/lakefront/core/models.py +50 -0
- lakefront-0.1.0/pkg/tui/src/lakefront/tui/__init__.py +224 -0
- lakefront-0.1.0/pyproject.toml +46 -0
- lakefront-0.1.0/setup.cfg +4 -0
lakefront-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Piotr Nowakowski
|
|
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.
|
lakefront-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lakefront
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lakehouse Observability Platform
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Dynamic: license-file
|
|
9
|
+
|
|
10
|
+
# Lakefront
|
|
11
|
+
|
|
12
|
+
Lakehouse Observability Platform
|
|
13
|
+
|
|
14
|
+
## About
|
|
15
|
+
|
|
16
|
+
Terminal-first tool for exploring and monitoring Parquet datasets.
|
|
17
|
+
Keyboard-driven TUI.
|
|
18
|
+
Powered by DuckDB.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install lakefront
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
lakefront db init # initialize local database
|
|
30
|
+
lakefront db reset # wipe and reinitialize
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
State is stored in `~/.config/lakefront/` — two files:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
~/.config/lakefront/
|
|
37
|
+
├── .db # projects + sources (DuckDB)
|
|
38
|
+
└── .project # active project name
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
lakefront ui # launch TUI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### TUI keybindings
|
|
48
|
+
|
|
49
|
+
| key | action |
|
|
50
|
+
| ----------- | -------------- |
|
|
51
|
+
| `n` | new project |
|
|
52
|
+
| `d` | delete project |
|
|
53
|
+
| `enter` | open project |
|
|
54
|
+
| `backspace` | go back |
|
|
55
|
+
| `q` | quit |
|
|
56
|
+
|
|
57
|
+
## Structure
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
pkg/
|
|
61
|
+
├── core/ # ProjectManager, Explorer, models
|
|
62
|
+
├── tui/ # Textual screens and widgets
|
|
63
|
+
└── cli/ # Typer entrypoint
|
|
64
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Lakefront
|
|
2
|
+
|
|
3
|
+
Lakehouse Observability Platform
|
|
4
|
+
|
|
5
|
+
## About
|
|
6
|
+
|
|
7
|
+
Terminal-first tool for exploring and monitoring Parquet datasets.
|
|
8
|
+
Keyboard-driven TUI.
|
|
9
|
+
Powered by DuckDB.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install lakefront
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
lakefront db init # initialize local database
|
|
21
|
+
lakefront db reset # wipe and reinitialize
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
State is stored in `~/.config/lakefront/` — two files:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
~/.config/lakefront/
|
|
28
|
+
├── .db # projects + sources (DuckDB)
|
|
29
|
+
└── .project # active project name
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
lakefront ui # launch TUI
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### TUI keybindings
|
|
39
|
+
|
|
40
|
+
| key | action |
|
|
41
|
+
| ----------- | -------------- |
|
|
42
|
+
| `n` | new project |
|
|
43
|
+
| `d` | delete project |
|
|
44
|
+
| `enter` | open project |
|
|
45
|
+
| `backspace` | go back |
|
|
46
|
+
| `q` | quit |
|
|
47
|
+
|
|
48
|
+
## Structure
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pkg/
|
|
52
|
+
├── core/ # ProjectManager, Explorer, models
|
|
53
|
+
├── tui/ # Textual screens and widgets
|
|
54
|
+
└── cli/ # Typer entrypoint
|
|
55
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lakefront
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lakehouse Observability Platform
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Dynamic: license-file
|
|
9
|
+
|
|
10
|
+
# Lakefront
|
|
11
|
+
|
|
12
|
+
Lakehouse Observability Platform
|
|
13
|
+
|
|
14
|
+
## About
|
|
15
|
+
|
|
16
|
+
Terminal-first tool for exploring and monitoring Parquet datasets.
|
|
17
|
+
Keyboard-driven TUI.
|
|
18
|
+
Powered by DuckDB.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install lakefront
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
lakefront db init # initialize local database
|
|
30
|
+
lakefront db reset # wipe and reinitialize
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
State is stored in `~/.config/lakefront/` — two files:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
~/.config/lakefront/
|
|
37
|
+
├── .db # projects + sources (DuckDB)
|
|
38
|
+
└── .project # active project name
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
lakefront ui # launch TUI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### TUI keybindings
|
|
48
|
+
|
|
49
|
+
| key | action |
|
|
50
|
+
| ----------- | -------------- |
|
|
51
|
+
| `n` | new project |
|
|
52
|
+
| `d` | delete project |
|
|
53
|
+
| `enter` | open project |
|
|
54
|
+
| `backspace` | go back |
|
|
55
|
+
| `q` | quit |
|
|
56
|
+
|
|
57
|
+
## Structure
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
pkg/
|
|
61
|
+
├── core/ # ProjectManager, Explorer, models
|
|
62
|
+
├── tui/ # Textual screens and widgets
|
|
63
|
+
└── cli/ # Typer entrypoint
|
|
64
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
lakefront.egg-info/PKG-INFO
|
|
5
|
+
lakefront.egg-info/SOURCES.txt
|
|
6
|
+
lakefront.egg-info/dependency_links.txt
|
|
7
|
+
lakefront.egg-info/top_level.txt
|
|
8
|
+
pkg/cli/src/lakefront/cli/__init__.py
|
|
9
|
+
pkg/core/src/lakefront/core/__init__.py
|
|
10
|
+
pkg/core/src/lakefront/core/bootstrap.py
|
|
11
|
+
pkg/core/src/lakefront/core/config.py
|
|
12
|
+
pkg/core/src/lakefront/core/explorer.py
|
|
13
|
+
pkg/core/src/lakefront/core/manager.py
|
|
14
|
+
pkg/core/src/lakefront/core/models.py
|
|
15
|
+
pkg/tui/src/lakefront/tui/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pkg
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
app = typer.Typer()
|
|
6
|
+
projects = typer.Typer()
|
|
7
|
+
app.add_typer(projects, name="projects")
|
|
8
|
+
db_app = typer.Typer()
|
|
9
|
+
app.add_typer(db_app, name="db")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@db_app.command(
|
|
16
|
+
help="Initialize the lakefront database. Run this command before using any other commands."
|
|
17
|
+
)
|
|
18
|
+
def init():
|
|
19
|
+
from lakefront.core import bootstrap
|
|
20
|
+
|
|
21
|
+
console.print("[bold green]Initializing database...[/]")
|
|
22
|
+
bootstrap()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@db_app.command(help="Open a duckdb shell connected to the lakefront database.")
|
|
26
|
+
def shell():
|
|
27
|
+
"""Open a duckdb shell connected to the lakefront database.
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
$ lakefront db shell
|
|
31
|
+
$ db: SELECT * FROM projects;
|
|
32
|
+
$ db: show tables;
|
|
33
|
+
$ db: describe projects;
|
|
34
|
+
$ db:.exit
|
|
35
|
+
"""
|
|
36
|
+
import subprocess
|
|
37
|
+
|
|
38
|
+
from lakefront.core.config import DB_PATH
|
|
39
|
+
|
|
40
|
+
subprocess.run(["duckdb", str(DB_PATH)], check=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@db_app.command()
|
|
44
|
+
def reset():
|
|
45
|
+
from lakefront.core import bootstrap, teardown
|
|
46
|
+
|
|
47
|
+
console.print("[bold green]Re-initializing database...[/]")
|
|
48
|
+
teardown()
|
|
49
|
+
bootstrap()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@projects.command("list")
|
|
53
|
+
def list_projects():
|
|
54
|
+
from lakefront.core import ProjectManager
|
|
55
|
+
|
|
56
|
+
pm = ProjectManager()
|
|
57
|
+
projects = pm.list()
|
|
58
|
+
table = Table(title="Projects", show_header=True, header_style="bold white")
|
|
59
|
+
table.add_column("ID", justify="right", style="white", width=6)
|
|
60
|
+
table.add_column("Name", style="dim", width=30)
|
|
61
|
+
table.add_column("Created At", justify="right", style="dim")
|
|
62
|
+
for p in projects:
|
|
63
|
+
table.add_row(str(p.id), p.name, p.created_at.strftime("%Y-%m-%d %H:%M:%S"))
|
|
64
|
+
console.print(table)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command()
|
|
68
|
+
def version():
|
|
69
|
+
from lakefront.core import get_version
|
|
70
|
+
|
|
71
|
+
console.print(f"[white]lakefront v{get_version()} [/]")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command()
|
|
75
|
+
def ui():
|
|
76
|
+
from lakefront.tui import LakeFrontApp
|
|
77
|
+
|
|
78
|
+
LakeFrontApp().run()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .bootstrap import bootstrap, teardown
|
|
2
|
+
from .explorer import Explorer
|
|
3
|
+
from .manager import ProjectManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_version() -> str:
|
|
7
|
+
import importlib.metadata as pkg
|
|
8
|
+
|
|
9
|
+
return pkg.version("lakefront-core")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ["ProjectManager", "Explorer", "bootstrap", "teardown", "get_version"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# polon/core/manager.py
|
|
2
|
+
|
|
3
|
+
from .config import CONFIG_DIR, DB_PATH, PROJECTS_DIR, metadb
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def bootstrap():
|
|
7
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
8
|
+
PROJECTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
9
|
+
|
|
10
|
+
with metadb() as db:
|
|
11
|
+
db.execute("""
|
|
12
|
+
CREATE SEQUENCE IF NOT EXISTS projects_id_seq;
|
|
13
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
14
|
+
id INTEGER PRIMARY KEY DEFAULT nextval('projects_id_seq'),
|
|
15
|
+
name TEXT NOT NULL UNIQUE,
|
|
16
|
+
path TEXT NOT NULL,
|
|
17
|
+
created_at TIMESTAMP DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
CREATE SEQUENCE IF NOT EXISTS conf_id_seq;
|
|
20
|
+
CREATE TABLE IF NOT EXISTS conf (
|
|
21
|
+
id INTEGER PRIMARY KEY DEFAULT nextval('conf_id_seq'),
|
|
22
|
+
key TEXT NOT NULL UNIQUE,
|
|
23
|
+
value TEXT NOT NULL,
|
|
24
|
+
created_at TIMESTAMP DEFAULT now()
|
|
25
|
+
);
|
|
26
|
+
""")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def teardown():
|
|
30
|
+
if not DB_PATH.exists():
|
|
31
|
+
return
|
|
32
|
+
with metadb() as db:
|
|
33
|
+
db.execute("DROP TABLE IF EXISTS projects")
|
|
34
|
+
db.execute("DROP SEQUENCE IF EXISTS projects_id_seq")
|
|
35
|
+
DB_PATH.unlink(missing_ok=True)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import duckdb
|
|
4
|
+
|
|
5
|
+
CONFIG_DIR = Path("~/.config/lakefront").expanduser()
|
|
6
|
+
DB_PATH = CONFIG_DIR / ".db"
|
|
7
|
+
ACTIVE_PATH = CONFIG_DIR / ".project"
|
|
8
|
+
PROJECTS_DIR = Path("~/.local/share/lakefront/projects").expanduser()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def metadb():
|
|
12
|
+
return duckdb.connect(str(DB_PATH))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_project_path(name: str) -> Path:
|
|
16
|
+
path = PROJECTS_DIR / name
|
|
17
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
return path
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .config import ACTIVE_PATH, DB_PATH
|
|
2
|
+
from .models import ProjectDTO, ProjectRepository
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ProjectManager:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
if not DB_PATH.exists():
|
|
8
|
+
raise RuntimeError("database not initialized. run: lakefront db init")
|
|
9
|
+
self.repo = ProjectRepository()
|
|
10
|
+
|
|
11
|
+
def create(self, name: str) -> ProjectDTO:
|
|
12
|
+
return self.repo.save(name)
|
|
13
|
+
|
|
14
|
+
def list(self) -> list[ProjectDTO]:
|
|
15
|
+
return self.repo.find_all()
|
|
16
|
+
|
|
17
|
+
def delete(self, name: str):
|
|
18
|
+
self.repo.delete(name)
|
|
19
|
+
if self.active() == name:
|
|
20
|
+
ACTIVE_PATH.unlink(missing_ok=True)
|
|
21
|
+
|
|
22
|
+
def switch(self, name: str):
|
|
23
|
+
if not self.repo.find_by_name(name):
|
|
24
|
+
raise ValueError(f"project '{name}' does not exist")
|
|
25
|
+
ACTIVE_PATH.write_text(name)
|
|
26
|
+
|
|
27
|
+
def active(self) -> str | None:
|
|
28
|
+
if ACTIVE_PATH.exists():
|
|
29
|
+
return ACTIVE_PATH.read_text().strip()
|
|
30
|
+
return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from .config import make_project_path, metadb
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ProjectDTO:
|
|
9
|
+
id: int
|
|
10
|
+
name: str
|
|
11
|
+
path: str
|
|
12
|
+
created_at: datetime
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def from_record(cls, record: tuple) -> "ProjectDTO":
|
|
16
|
+
return cls(id=record[0], name=record[1], path=record[2], created_at=record[3])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProjectConfig:
|
|
20
|
+
def __init__(self, project_id: int):
|
|
21
|
+
self.project_id = project_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProjectRepository:
|
|
25
|
+
def find_by_name(self, name: str) -> ProjectDTO | None:
|
|
26
|
+
with metadb() as db:
|
|
27
|
+
row = db.execute(
|
|
28
|
+
"SELECT id, name, path, created_at FROM projects WHERE name = ?", [name]
|
|
29
|
+
).fetchone()
|
|
30
|
+
return ProjectDTO.from_record(row) if row else None
|
|
31
|
+
|
|
32
|
+
def find_all(self) -> list[ProjectDTO]:
|
|
33
|
+
with metadb() as db:
|
|
34
|
+
rows = db.execute(
|
|
35
|
+
"SELECT id, name, path, created_at FROM projects ORDER BY created_at DESC"
|
|
36
|
+
).fetchall()
|
|
37
|
+
return [ProjectDTO.from_record(row) for row in rows]
|
|
38
|
+
|
|
39
|
+
def save(self, name: str) -> ProjectDTO:
|
|
40
|
+
path = make_project_path(name)
|
|
41
|
+
with metadb() as db:
|
|
42
|
+
row = db.execute(
|
|
43
|
+
"INSERT INTO projects (name, path) VALUES (?, ?) RETURNING id, name, path, created_at",
|
|
44
|
+
[name, str(path)],
|
|
45
|
+
).fetchone()
|
|
46
|
+
return ProjectDTO.from_record(row)
|
|
47
|
+
|
|
48
|
+
def delete(self, name: str):
|
|
49
|
+
with metadb() as db:
|
|
50
|
+
db.execute("DELETE FROM projects WHERE name = ?", [name])
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from lakefront.core import ProjectManager
|
|
2
|
+
from textual.app import App, ComposeResult
|
|
3
|
+
from textual.binding import Binding
|
|
4
|
+
from textual.containers import Center, Horizontal, Vertical
|
|
5
|
+
from textual.screen import Screen
|
|
6
|
+
from textual.widgets import (
|
|
7
|
+
Button,
|
|
8
|
+
DataTable,
|
|
9
|
+
Footer,
|
|
10
|
+
Header,
|
|
11
|
+
Input,
|
|
12
|
+
Label,
|
|
13
|
+
ListItem,
|
|
14
|
+
ListView,
|
|
15
|
+
Static,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
pm = ProjectManager()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NewProjectScreen(Screen):
|
|
22
|
+
BINDINGS = [
|
|
23
|
+
Binding("escape", "app.pop_screen", "Cancel"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
def compose(self) -> ComposeResult:
|
|
27
|
+
yield Header()
|
|
28
|
+
yield Input(placeholder="project name")
|
|
29
|
+
yield Footer()
|
|
30
|
+
|
|
31
|
+
def on_input_submitted(self, event: Input.Submitted):
|
|
32
|
+
pm.create(event.value)
|
|
33
|
+
self.app.pop_screen()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ProjectView(Screen):
|
|
37
|
+
BINDINGS = [
|
|
38
|
+
Binding("backspace", "app.pop_screen", "Back"),
|
|
39
|
+
Binding("ctrl+r", "run_sql", "Run SQL"),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
CSS = """
|
|
43
|
+
#left { width: 25%; border-right: solid #39ff14 20%; }
|
|
44
|
+
#main { width: 1fr; }
|
|
45
|
+
#right { width: 25%; border-left: solid #39ff14 20%; }
|
|
46
|
+
|
|
47
|
+
#sources-list { height: 1fr; }
|
|
48
|
+
#stats-panel { height: 12; padding: 1; }
|
|
49
|
+
|
|
50
|
+
#sql-input { height: 3; }
|
|
51
|
+
#data-table { height: 1fr; }
|
|
52
|
+
|
|
53
|
+
Label {
|
|
54
|
+
color: #39ff14;
|
|
55
|
+
padding: 0 1;
|
|
56
|
+
}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, project_name: str, **kwargs):
|
|
60
|
+
super().__init__(**kwargs)
|
|
61
|
+
self.project_name = project_name
|
|
62
|
+
|
|
63
|
+
def compose(self) -> ComposeResult:
|
|
64
|
+
yield Header()
|
|
65
|
+
with Horizontal():
|
|
66
|
+
with Vertical(id="left"):
|
|
67
|
+
yield Label("sources")
|
|
68
|
+
yield ProjectListView(id="sources-list")
|
|
69
|
+
yield Label("statistics")
|
|
70
|
+
yield Static(id="stats-panel")
|
|
71
|
+
with Vertical(id="main"):
|
|
72
|
+
yield Input(placeholder="SELECT * FROM data LIMIT 100", id="sql-input")
|
|
73
|
+
yield DataTable(id="data-table")
|
|
74
|
+
with Vertical(id="right"):
|
|
75
|
+
yield Label("schema")
|
|
76
|
+
yield DataTable(id="schema-table")
|
|
77
|
+
yield Footer()
|
|
78
|
+
|
|
79
|
+
def on_list_view_selected(self, event): ...
|
|
80
|
+
|
|
81
|
+
def action_run_sql(self): ...
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ConfirmScreen(Screen):
|
|
85
|
+
BINDINGS = [
|
|
86
|
+
Binding("escape", "app.pop_screen", "Cancel"),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
def __init__(self, message: str, **kwargs):
|
|
90
|
+
super().__init__(**kwargs)
|
|
91
|
+
self.message = message
|
|
92
|
+
|
|
93
|
+
def compose(self) -> ComposeResult:
|
|
94
|
+
yield Header()
|
|
95
|
+
yield Label(self.message)
|
|
96
|
+
yield Button("Yes", id="yes", variant="error")
|
|
97
|
+
yield Button("No", id="no")
|
|
98
|
+
yield Footer()
|
|
99
|
+
|
|
100
|
+
def on_button_pressed(self, event: Button.Pressed):
|
|
101
|
+
self.dismiss(event.button.id == "yes")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
LOGO = r"""
|
|
105
|
+
__________ __
|
|
106
|
+
\______ \ ____ _____ | | __
|
|
107
|
+
| ___// __ \\__ \ | |/ /
|
|
108
|
+
| | \ ___/ / __ \| <
|
|
109
|
+
|____| \___ /____ /__|_ \
|
|
110
|
+
\/ \/ \/
|
|
111
|
+
lakehouse observability platform
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ProjectListView(ListView):
|
|
116
|
+
BINDINGS = [
|
|
117
|
+
Binding("j", "cursor_down", "Down", show=False),
|
|
118
|
+
Binding("k", "cursor_up", "Up", show=False),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SplashScreen(Screen):
|
|
123
|
+
CSS = """
|
|
124
|
+
.hidden { display: none; }
|
|
125
|
+
SplashScreen ListView {
|
|
126
|
+
width: 40;
|
|
127
|
+
height: auto;
|
|
128
|
+
align: center middle;
|
|
129
|
+
}
|
|
130
|
+
SplashScreen #center {
|
|
131
|
+
width: 40;
|
|
132
|
+
height: auto;
|
|
133
|
+
align: center middle;
|
|
134
|
+
}
|
|
135
|
+
SplashScreen {
|
|
136
|
+
align: center middle;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
SplashScreen Label {
|
|
140
|
+
width: 40;
|
|
141
|
+
content-align: center middle;
|
|
142
|
+
}
|
|
143
|
+
#logo {
|
|
144
|
+
color: #39ff14;
|
|
145
|
+
content-align: center middle;
|
|
146
|
+
width: 100%;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
BINDINGS = [
|
|
151
|
+
Binding("n", "new_project", "New"),
|
|
152
|
+
Binding("d", "delete_project", "Delete"),
|
|
153
|
+
Binding("enter", "open_project", "Open"),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
def action_cursor_down(self):
|
|
157
|
+
self.query_one(ListView).action_scroll_down()
|
|
158
|
+
|
|
159
|
+
def action_cursor_up(self):
|
|
160
|
+
self.query_one(ListView).action_scroll_up()
|
|
161
|
+
|
|
162
|
+
def on_mount(self):
|
|
163
|
+
lv = self.query_one(ListView)
|
|
164
|
+
lv.clear()
|
|
165
|
+
for project in pm.list():
|
|
166
|
+
lv.append(ListItem(Label(project.name)))
|
|
167
|
+
lv.focus()
|
|
168
|
+
|
|
169
|
+
def on_screen_resume(self):
|
|
170
|
+
self.on_mount()
|
|
171
|
+
|
|
172
|
+
def compose(self) -> ComposeResult:
|
|
173
|
+
yield Header()
|
|
174
|
+
yield Static(LOGO, id="logo")
|
|
175
|
+
yield Center(
|
|
176
|
+
Label("Projects"),
|
|
177
|
+
ProjectListView(),
|
|
178
|
+
Input(placeholder="delete? y/N", id="confirm", classes="hidden"),
|
|
179
|
+
)
|
|
180
|
+
yield Footer()
|
|
181
|
+
|
|
182
|
+
def on_list_view_selected(self, event: ListView.Selected):
|
|
183
|
+
name = str(event.item.query_one(Label).render())
|
|
184
|
+
self.app.push_screen(ProjectView(name))
|
|
185
|
+
|
|
186
|
+
def action_new_project(self):
|
|
187
|
+
self.app.push_screen(NewProjectScreen())
|
|
188
|
+
|
|
189
|
+
def on_input_submitted(self, event: Input.Submitted):
|
|
190
|
+
confirm = self.query_one("#confirm", Input)
|
|
191
|
+
confirm.add_class("hidden")
|
|
192
|
+
if event.value.lower() == "y" and self._pending_delete:
|
|
193
|
+
pm.delete(self._pending_delete)
|
|
194
|
+
self._pending_delete = None
|
|
195
|
+
self.on_mount() # refresh list
|
|
196
|
+
confirm.clear()
|
|
197
|
+
self.query_one(ListView).focus()
|
|
198
|
+
|
|
199
|
+
def action_delete_project(self):
|
|
200
|
+
lv = self.query_one(ListView)
|
|
201
|
+
if lv.highlighted_child:
|
|
202
|
+
self._pending_delete = str(lv.highlighted_child.query_one(Label).render())
|
|
203
|
+
confirm = self.query_one("#confirm", Input)
|
|
204
|
+
confirm.remove_class("hidden")
|
|
205
|
+
confirm.focus()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class LakeFrontApp(App):
|
|
209
|
+
TITLE = "Lakehouse Observability Platform"
|
|
210
|
+
BINDINGS = [
|
|
211
|
+
Binding("q", "quit", "Quit"),
|
|
212
|
+
]
|
|
213
|
+
CSS = """
|
|
214
|
+
ListItem > ListItem.--highlight {
|
|
215
|
+
background: #39ff14 20%;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
ListItem > ListItem.--highlight > Label {
|
|
219
|
+
color: #39ff14;
|
|
220
|
+
}
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def on_mount(self):
|
|
224
|
+
self.push_screen(SplashScreen())
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lakefront"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Lakehouse Observability Platform"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[tool.uv.workspace]
|
|
10
|
+
members = [
|
|
11
|
+
"pkg/core",
|
|
12
|
+
"pkg/cli",
|
|
13
|
+
"pkg/tui"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"deptry>=0.24.0",
|
|
19
|
+
"ipython>=8.38.0",
|
|
20
|
+
"pytest>=9.0.2",
|
|
21
|
+
"ruff>=0.15.1",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
pythonpath = "pkg/*/src"
|
|
26
|
+
addopts = "-vv --maxfail=1 --tb=short"
|
|
27
|
+
markers = [
|
|
28
|
+
"integration: tests requiring external services like Docker",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.deptry]
|
|
32
|
+
ignore = [
|
|
33
|
+
"DEP003"
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.ruff.lint]
|
|
37
|
+
select = ["I", "TID"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff.lint.isort]
|
|
40
|
+
force-sort-within-sections = false
|
|
41
|
+
known-first-party = ["lop"]
|
|
42
|
+
combine-as-imports = true
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
45
|
+
ban-relative-imports = "parents"
|
|
46
|
+
|