[project] extract tui code into own module, also extraxt (db) models into their own module
Some checks failed
ci/woodpecker/push/workflow Pipeline failed

This commit is contained in:
bronsen 2025-12-26 18:53:14 +01:00
parent d58ae7aad1
commit 173d23addf
3 changed files with 155 additions and 95 deletions

View file

@ -2,103 +2,47 @@
This is where the application is implemented.
"""
from typing import Literal, final, override
import logging
from textual.app import App, ComposeResult
from textual.containers import HorizontalGroup
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import (
Button,
DataTable,
Footer,
Header,
Input,
Static,
)
from sqlmodel import Session
from textual.app import App
from .database import create_db_and_tables, engine
from .models import Teilchen, make_teilchen_input
from .tui import AddInventoryScreen
logger = logging.getLogger(__name__)
@final
class SearchBar(Static):
DEFAULT_CSS = """
#teilchen-input {
width: 4fr;
}
#button-search, #button-add {
width: 1fr;
}
"""
@override
def compose(self) -> ComposeResult:
with HorizontalGroup(id="search-bar-widget"):
yield Input(
placeholder="Enter Teilchen information: name, description, #tags",
tooltip=(
"This is a free-form field: Enter a name and "
"description any way you like. You should use #hashtags for any "
"meta information."
),
id="teilchen-input",
type="text",
)
yield Button("Add", variant="success", classes="search-bar-buttons", id="button-add")
yield Button(
"Search",
variant="default",
classes="search-bar-buttons",
id="button-search",
)
TeilchenDatum = tuple[int, str, str, str, str]
TeilchenHeader = tuple[
Literal["pk"],
Literal["Name"],
Literal["Description"],
Literal["Number"],
Literal["Tags"],
]
FAKE_DATA: list[TeilchenHeader | TeilchenDatum] = [
("pk", "Name", "Description", "Number", "Tags"),
(0, "Name0", "Description0", "9000", "#tag0 #tag00 #tag000"),
(1, "Name1", "Description1", "9001", "#tag1 #tag11 #tag111"),
(2, "Name2", "Description2", "9002", "#tag2 #tag22 #tag222"),
(3, "Name3", "Description3", "9003", "#tag3 #tag33 #tag333"),
(4, "Name4", "Description4", "9004", "#tag4 #tag44 #tag444"),
(5, "Name5", "Description5", "9005", "#tag5 #tag55 #tag555"),
]
@final
class SearchResults(Widget):
@override
def compose(self) -> ComposeResult:
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
async def on_mount(self) -> None:
table: DataTable[None] = self.query_one(DataTable[None])
_ = table.add_columns(*FAKE_DATA[0]) # pyright: ignore[reportArgumentType]
_ = table.add_rows(FAKE_DATA[1:]) # pyright: ignore[reportArgumentType]
class AddInventoryScreen(Screen[None]):
@override
def compose(self) -> ComposeResult:
yield Header()
yield SearchBar()
yield SearchResults()
yield Footer()
@final
class SammlerApp(App[None]):
async def on_mount(self) -> None:
create_db_and_tables()
_ = self.push_screen(AddInventoryScreen())
FAKE_INPUT = """
Ein Teilchen. #Tag03 #tag12 "Dieses Teilchen ist nur zum testen" #tag1 #tag2
""".strip(" \n")
def try_this():
teilchen_data = make_teilchen_input(FAKE_INPUT)
if not teilchen_data:
logger.error("oh no!")
with Session(engine) as session:
db_teilchen = Teilchen.model_validate(teilchen_data)
session.add(db_teilchen)
session.commit()
session.refresh(db_teilchen)
print(f"{db_teilchen=}")
def main() -> None:
app = SammlerApp()
app.run()
if __name__ == "__main__":
main()

View file

@ -1,23 +1,57 @@
import logging
import re
import uuid
from natsort import natsorted
from sqlmodel import (
Field,
SQLModel,
)
logger = logging.getLogger(__name__)
class TeilchenInput(SQLModel):
class TeilchenBase(SQLModel):
text: str
class Teilchen(TeilchenInput, table=True):
class TeilchenCreate(TeilchenBase):
description: str | None
name: str = Field(index=True)
number: int = Field(default=1)
tags: str | None
text: str # The original input as entered by the user
class Teilchen(TeilchenCreate, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid7, primary_key=True)
name: str = Field(index=True)
description: str | None
tags: str | None
number: int = Field(default=1)
def make_teilchen_input(text: str) -> TeilchenCreate | None:
if not text:
logger.error("Empty text.")
return None
name_end = text.find(".")
name = text[0:name_end]
if not name:
logger.error("Could not extract name.")
return None
description_start = text.find('"', name_end + 1)
description_end = text.find('"', description_start + 1)
description = text[description_start:description_end]
if not description:
logger.warning("Could not extract description.")
tags = re.findall(r"#\w+", text.lower())
if not tags:
logger.warning("No tags found in text")
return TeilchenCreate(
name=name,
description=description,
tags=" ".join(natsorted(tags)),
text=text,
)

View file

@ -0,0 +1,82 @@
from typing import Literal, final, override
from textual.app import ComposeResult
from textual.containers import HorizontalGroup
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import DataTable, Footer, Header, Static, Input, Button
@final
class SearchBar(Static):
DEFAULT_CSS = """
#teilchen-input {
width: 4fr;
}
#button-search, #button-add {
width: 1fr;
}
"""
@override
def compose(self) -> ComposeResult:
with HorizontalGroup(id="search-bar-widget"):
yield Input(
placeholder="Enter Teilchen information: name, description, #tags",
tooltip=(
"This is a free-form field: Enter a name and "
"description any way you like. You should use #hashtags for any "
"meta information."
),
id="teilchen-input",
type="text",
)
yield Button("Add", variant="success", classes="search-bar-buttons", id="button-add")
yield Button(
"Search",
variant="default",
classes="search-bar-buttons",
id="button-search",
)
TeilchenDatum = tuple[int, str, str, str, str]
TeilchenHeader = tuple[
Literal["pk"],
Literal["Name"],
Literal["Description"],
Literal["Number"],
Literal["Tags"],
]
FAKE_DATA: list[TeilchenHeader | TeilchenDatum] = [
("pk", "Name", "Description", "Number", "Tags"),
(0, "Name0", "Description0", "9000", "#tag0 #tag00 #tag000"),
(1, "Name1", "Description1", "9001", "#tag1 #tag11 #tag111"),
(2, "Name2", "Description2", "9002", "#tag2 #tag22 #tag222"),
(3, "Name3", "Description3", "9003", "#tag3 #tag33 #tag333"),
(4, "Name4", "Description4", "9004", "#tag4 #tag44 #tag444"),
(5, "Name5", "Description5", "9005", "#tag5 #tag55 #tag555"),
]
@final
class SearchResults(Widget):
@override
def compose(self) -> ComposeResult:
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
async def on_mount(self) -> None:
table: DataTable[None] = self.query_one(DataTable[None])
_ = table.add_columns(*FAKE_DATA[0]) # pyright: ignore[reportArgumentType]
_ = table.add_rows(FAKE_DATA[1:]) # pyright: ignore[reportArgumentType]
class AddInventoryScreen(Screen[None]):
@override
def compose(self) -> ComposeResult:
yield Header()
yield SearchBar()
yield SearchResults()
yield Footer()