Compare commits

..

8 commits

Author SHA1 Message Date
bronsen
f41f8e28e5 release: bump version to 0.3.3
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
ci/woodpecker/tag/workflow Pipeline was successful
2026-02-19 20:49:09 +01:00
bronsen
c3b749ec37 codestyle: ty needs less help here too 2026-02-19 20:44:56 +01:00
bronsen
9b04902825 models: it's not an error if we can continue operating 2026-02-19 20:44:18 +01:00
bronsen
49a21ca9cf models: be even more defensive when parsing input
also we finally read the documentation for str.find()
https://docs.python.org/3/library/stdtypes.html#str.find
2026-02-19 20:42:50 +01:00
bronsen
0ae116afa9 tests: finally another test! 2026-02-19 20:38:29 +01:00
bronsen
b0e7089389 codestyle: ty needs less hinting 2026-02-18 20:40:58 +01:00
bronsen
eb51024ca0 database: inhibit echoing to console
This interferes very much with the app.
2026-02-18 20:40:17 +01:00
bronsen
c3d5983d23 codestyle: remove assignment to _: we no longer need to quieten the LSP 2026-02-18 18:23:38 +01:00
7 changed files with 69 additions and 11 deletions

View file

@ -1,6 +1,6 @@
[project] [project]
name = "teilchensammler-cli" name = "teilchensammler-cli"
version = "0.3.2" version = "0.3.3"
description = "Build up and maintain an inventory of electronics parts and tools." description = "Build up and maintain an inventory of electronics parts and tools."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12,<4.0" requires-python = ">=3.12,<4.0"

View file

@ -3,7 +3,7 @@ from sqlmodel import SQLModel, create_engine
sqlite_url = os.environ.get("DATABASE_URL", "sqlite:///database.db") sqlite_url = os.environ.get("DATABASE_URL", "sqlite:///database.db")
engine = create_engine(sqlite_url, echo=True) engine = create_engine(sqlite_url, echo=False)
def create_db_and_tables(): def create_db_and_tables():

View file

@ -17,7 +17,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SammlerApp(App[None]): class SammlerApp(App):
async def on_mount(self) -> None: async def on_mount(self) -> None:
create_db_and_tables() create_db_and_tables()
_ = self.push_screen(AddInventoryScreen()) _ = self.push_screen(AddInventoryScreen())

View file

@ -37,14 +37,22 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None:
import re import re
from natsort import natsorted from natsort import natsorted
text = text.strip()
if not text: if not text:
logger.error("Empty text.") logger.error("Empty text.")
return None return None
name_end = text.find(".") name_end = text.find(".")
if name_end == -1: # "." not in text
logger.warning("Missing period '.' from text.")
logger.debug("Could not find period symbol in input: %s", text)
return None
else:
name = text[0:name_end] name = text[0:name_end]
if not name: if not name:
logger.error("Could not extract name.") logger.warning("Could not extract name.")
logger.debug("Could not extract name from input: %s", text) logger.debug("Could not extract name from input: %s", text)
return None return None

View file

@ -7,7 +7,7 @@ from textual.widgets import Button, DataTable, Footer, Header, Input, Static
from .models import load_initial_data from .models import load_initial_data
FAKE_DATA_HEADER = "pk Name Description Number Tags".split() TEILCHEN_DATA_HEADER = "pk Name Description Number Tags".split()
class SearchBar(Static): class SearchBar(Static):
@ -46,12 +46,12 @@ class SearchResults(Widget):
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True) yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
async def on_mount(self) -> None: async def on_mount(self) -> None:
table: DataTable[None] = self.query_one(DataTable[None]) table: DataTable = self.query_one(DataTable)
_ = table.add_columns(*FAKE_DATA_HEADER) table.add_columns(*TEILCHEN_DATA_HEADER)
_ = table.add_rows(await load_initial_data()) table.add_rows(await load_initial_data())
class AddInventoryScreen(Screen[None]): class AddInventoryScreen(Screen):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
yield SearchBar() yield SearchBar()

View file

@ -1,4 +1,54 @@
from typing import TypedDict
import pytest
import logging
from teilchensammler_cli.models import TeilchenCreate, make_teilchen_input
logger = logging.getLogger(__name__)
def test_initial_layout(snap_compare): def test_initial_layout(snap_compare):
from teilchensammler_cli.main import app from teilchensammler_cli.main import app
assert snap_compare(app, terminal_size=(130, 40)) assert snap_compare(app, terminal_size=(130, 40))
class Teilchen(TypedDict):
"""The things I do for my LSP..."""
name: str
description: str
tags: str
text: str
number: int
empty_teilchen: Teilchen = {
"name": "",
"description": "",
"tags": "",
"text": "",
"number": 1,
}
@pytest.mark.parametrize(
"example_input,expected",
[
("", None),
("a", None),
("a.", {"name": "a", "text": "a."}),
("aa", None),
("aa.", {"name": "aa", "text": "aa."}),
],
)
async def test_teilchendata_must_include_period(
example_input: str, expected: dict[str, str | int] | None
) -> None:
thing = expected
if expected:
arguments = empty_teilchen | expected
thing = TeilchenCreate(**arguments) # ty:ignore[invalid-argument-type]
assert await make_teilchen_input(example_input) == thing

2
uv.lock generated
View file

@ -1249,7 +1249,7 @@ wheels = [
[[package]] [[package]]
name = "teilchensammler-cli" name = "teilchensammler-cli"
version = "0.3.2" version = "0.3.3"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "ciso8601" }, { name = "ciso8601" },