Compare commits

..

No commits in common. "98ebb69fa4552cc89a18f20a3b15b12aa7b721f9" and "dd79123d7bd8557f6eb7cb50383c21a242faa958" have entirely different histories.

10 changed files with 208 additions and 215 deletions

View file

@ -108,8 +108,10 @@ Please don't: this is my little human-created thing.
Where `segment` is one of: major, minor, patch, stable, alpha, beta, rc, Where `segment` is one of: major, minor, patch, stable, alpha, beta, rc,
post, dev post, dev
* commit * commit
* When we really really want to release: * push commits
* `just release {Release Name can be several words}` * use lazygit to create the new version tag
* remember to prefix it with "v"
* push the tags, using lazygit
---- ----

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before After
Before After

View file

@ -9,6 +9,14 @@ default:
setup: setup:
uv sync uv sync
# builds a package
build:
uv build --wheel --clear
# upload to Package Registry
upload: build
uv run twine upload --repository code.cbo --config-file ~/.config/pypirc dist/*.whl
# console to observe log messages # console to observe log messages
console: console:
uv run textual console uv run textual console
@ -21,16 +29,16 @@ run-console:
run: run:
uv run python -m {{ the_app }} uv run python -m {{ the_app }}
# Update dependencies to new versions
update-deps:
uv lock --upgrade
uv sync
# export dependencies into requirements files # export dependencies into requirements files
exports-deps: exports-deps:
uv export {{ uv_export_options }} --output-file requirements.txt uv export {{ uv_export_options }} --output-file requirements.txt
uv export {{ uv_export_options }} --output-file requirements.dev.txt --only-dev uv export {{ uv_export_options }} --output-file requirements.dev.txt --only-dev
# Update dependencies to new versions
update-deps:
uv lock --upgrade
uv sync
# Run tests, ARGS are passed-through to pytest # Run tests, ARGS are passed-through to pytest
test *ARGS: test *ARGS:
uv run pytest tests.py -m "not final" {{ ARGS }} uv run pytest tests.py -m "not final" {{ ARGS }}
@ -42,18 +50,26 @@ alltests *ARGS:
coverage: coverage:
uv run pytest tests.py --cov=src/ --cov-report term --cov-report xml --cov-report html --cov-config pyproject.toml uv run pytest tests.py --cov=src/ --cov-report term --cov-report xml --cov-report html --cov-config pyproject.toml
# lint python, markdown, wheel file names, prek config # run python and markdown
lint: lint:
uv run ruff check . uv run ruff check .
markdownlint-cli2 . markdownlint-cli2 .
uv run twine check --strict dist/* uv run twine check --strict dist/*
prek validate-config prek.toml
release *release_name: # pretend we are CI
ci: lint
prek run --all-files
# woodpecker-cli exec "whatever"
[private]
bump segment:
uv version --bump {{ segment }}
[private]
release segment *release_name:
#!/usr/bin/env fish #!/usr/bin/env fish
set -l release_name {{ release_name }}
test -f releasenotes.md; and set -l body "$(cat releasenotes.md)"; or set -l body "" test -f releasenotes.md; and set -l body "$(cat releasenotes.md)"; or set -l body ""
set -l tag (uv version --short --output-format text) set -l tag (uv version --short --output-format text --bump {{ segment }})
git add pyproject.toml uv.lock git add pyproject.toml uv.lock
git commit -m "Release version $tag" git commit -m "Release version $tag"
@ -62,15 +78,7 @@ release *release_name:
git push git push
git push origin tag "v$tag" git push origin tag "v$tag"
just build
fj release create "v$tag: $release_name" --tag "v$tag" --attach dist/*.whl --body "$body"
# just upload
# builds a package
build:
uv build --wheel --clear uv build --wheel --clear
# upload to Package Registry fj release create "v$tag: $release_name" --prerelease --tag "v$tag" --attach dist/*.whl --body "$body"
upload: # uv run twine upload --config-file ~/.config/pypirc --repository code.cbo dist/*.whl
uv run twine upload --repository code.cbo --config-file ~/.config/pypirc dist/*.whl

View file

@ -1,7 +1,6 @@
[env] [env]
'_'.python.venv = { path = ".venv", create = true } '_'.python.venv = { path = ".venv", create = true }
DATABASE_URL = "sqlite:///database.db" DATABASE_URL = "sqlite:///database.db"
PREK_COLOR = "never"
[tools] [tools]
difftastic = "latest" difftastic = "latest"

View file

@ -1,7 +1,5 @@
#:tombi schema.strict = false #:tombi schema.strict = false
# see also: https://prek.j178.dev/builtin/
exclude = { glob = ["__snapshots__/**", "__pycache__", "dist/**"] } exclude = { glob = ["__snapshots__/**", "__pycache__", "dist/**"] }
[[repos]] [[repos]]
@ -9,10 +7,19 @@ repo = "builtin"
hooks = [ hooks = [
{ id = "check-case-conflict" }, { id = "check-case-conflict" },
{ id = "check-merge-conflict" }, { id = "check-merge-conflict" },
{ id = "check-json" },
{ id = "check-symlinks" },
{ id = "check-toml" }, { id = "check-toml" },
{ id = "check-yaml", args = [
# "--allow-multiple-documents", #https://prek.j178.dev/builtin/#check-yaml
] },
{ id = "end-of-file-fixer" }, { id = "end-of-file-fixer" },
{ id = "mixed-line-ending", args = [ "--fix=lf" ] }, { id = "mixed-line-ending", args = [
{ id = "trailing-whitespace", args = [ "--markdown-linebreak-ext=md" ] }, "--fix=lf", # https://prek.j178.dev/builtin/#mixed-line-ending
] },
{ id = "trailing-whitespace", args = [
"--markdown-linebreak-ext=md", # https://prek.j178.dev/builtin/#trailing-whitespace
] },
] ]
[[repos]] [[repos]]

View file

@ -1,6 +1,6 @@
[project] [project]
name = "teilchensammler-cli" name = "teilchensammler-cli"
version = "0.5.0" version = "0.4.9"
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.14,<4.0" requires-python = ">=3.14,<4.0"

View file

@ -4,16 +4,11 @@ This is where the application is implemented.
import logging import logging
from textual import on from textual.app import App
from textual.app import App, ComposeResult
from textual.containers import HorizontalGroup
from textual.logging import TextualHandler from textual.logging import TextualHandler
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import Button, DataTable, Footer, Header, Input, Static
from .database import create_db_and_tables from .database import create_db_and_tables
from .models import add_to_database, load_initial_data, make_teilchen_input from .tui import AddInventoryScreen
logging.basicConfig( logging.basicConfig(
level="NOTSET", level="NOTSET",
@ -21,8 +16,6 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TEILCHEN_DATA_HEADER = "pk Name Description Number Tags".split()
class SammlerApp(App): class SammlerApp(App):
async def on_mount(self) -> None: async def on_mount(self) -> None:
@ -30,68 +23,6 @@ class SammlerApp(App):
self.push_screen(AddInventoryScreen()) self.push_screen(AddInventoryScreen())
class SearchBar(Static):
DEFAULT_CSS = """
#teilchen-input {
width: 4fr;
}
#button-search, #button-add {
width: 1fr;
}
"""
def compose(self) -> ComposeResult:
with HorizontalGroup(id="search-bar-widget"):
yield Input(
placeholder='This is a name. "This is the description" #these #are #tags',
tooltip=(
"Enter a name followed by a period, then a description "
'enclosed in double quotes ("). You should use #hashtags for any meta information. '
"Hashtags can be placed anywhere."
),
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",
)
@on(Input.Submitted)
async def parse_input(self, event: Input.Submitted) -> None:
if not (tc := await make_teilchen_input(event.value)):
return
event.input.value = ""
teilchen = await add_to_database(tc)
self.screen.query_one(DataTable).add_row(
teilchen.id, teilchen.name, teilchen.description, teilchen.number, teilchen.tags
)
class SearchResults(Widget):
def compose(self) -> ComposeResult:
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
async def on_mount(self) -> None:
table: DataTable = self.query_one(DataTable)
table.add_columns(*TEILCHEN_DATA_HEADER)
table.add_rows(await load_initial_data())
class AddInventoryScreen(Screen):
def compose(self) -> ComposeResult:
yield Header()
yield SearchBar()
yield SearchResults()
yield Footer()
# so we can import it without running it # so we can import it without running it
app = SammlerApp() app = SammlerApp()

View file

@ -3,14 +3,12 @@ import uuid
from sqlmodel import ( from sqlmodel import (
Field, Field,
Sequence,
Session,
SQLModel, SQLModel,
Session,
select, select,
Sequence,
) )
from .database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -37,7 +35,6 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None:
Returns `None` otherwise. Returns `None` otherwise.
""" """
import re import re
from natsort import natsorted from natsort import natsorted
text = text.strip() text = text.strip()
@ -91,13 +88,3 @@ async def load_initial_data() -> Sequence[Teilchen]:
) # ty:ignore[no-matching-overload] ) # ty:ignore[no-matching-overload]
all_teilchen = session.exec(statement).all() all_teilchen = session.exec(statement).all()
return all_teilchen return all_teilchen
async def add_to_database(tc: TeilchenCreate) -> Teilchen:
with Session(engine) as session:
teilchen = Teilchen.model_validate(tc)
session.add(teilchen)
session.commit()
session.refresh(teilchen)
return teilchen

View file

@ -0,0 +1,59 @@
from textual.app import 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 .models import load_initial_data
TEILCHEN_DATA_HEADER = "pk Name Description Number Tags".split()
class SearchBar(Static):
DEFAULT_CSS = """
#teilchen-input {
width: 4fr;
}
#button-search, #button-add {
width: 1fr;
}
"""
def compose(self) -> ComposeResult:
with HorizontalGroup(id="search-bar-widget"):
yield Input(
placeholder='This is a name. "This is the description" #these #are #tags',
tooltip=(
"Enter a name followed by a period, then a description "
'enclosed in double quotes ("). You should use #hashtags for any meta information. '
"Hashtags can be placed anywhere."
),
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",
)
class SearchResults(Widget):
def compose(self) -> ComposeResult:
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
async def on_mount(self) -> None:
table: DataTable = self.query_one(DataTable)
table.add_columns(*TEILCHEN_DATA_HEADER)
table.add_rows(await load_initial_data())
class AddInventoryScreen(Screen):
def compose(self) -> ComposeResult:
yield Header()
yield SearchBar()
yield SearchResults()
yield Footer()

2
uv.lock generated
View file

@ -1153,7 +1153,7 @@ wheels = [
[[package]] [[package]]
name = "teilchensammler-cli" name = "teilchensammler-cli"
version = "0.5.0" version = "0.4.9"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "ciso8601" }, { name = "ciso8601" },