Merge branch 'devel' into feat/container

This commit is contained in:
Alivecow 2025-02-26 21:28:56 +01:00
commit 328d76e7da
10 changed files with 193 additions and 19 deletions

14
poetry.lock generated
View file

@ -243,6 +243,18 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "tinydb"
version = "3.15.2"
description = "TinyDB is a tiny, document oriented database optimized for your happiness :)"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "tinydb-3.15.2-py2.py3-none-any.whl", hash = "sha256:1087ade5300c47dbf9539d9f6dafd53115bd5e85a67d480d8188bdbfa2d9eaf4"},
{file = "tinydb-3.15.2.tar.gz", hash = "sha256:f273d9b6d8b1b5e1d094a6eb8b72851b39b81099293344132c73332b60e3b893"},
]
[[package]]
name = "tomli"
version = "2.2.1"
@ -307,4 +319,4 @@ watchdog = ["watchdog (>=2.3)"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10"
content-hash = "45718b68919d6fafaaed35cd58dbbb244f8ebb0f0c73de8050cd04b8cae82738"
content-hash = "29ea72f4fd9859486c77dc4fd0668e0c55b618c56c01c23a0e2ce0a2cf75d026"

View file

@ -2,19 +2,17 @@
name = "senju"
version = "0.1.0"
description = "API / Webservice for Phrases/Words/Kanji/Haiku"
authors = [
{name = "PlexSheep",email = "software@cscherr.de"}
]
authors = [{ name = "Christoph J. Scherr", email = "software@cscherr.de" }]
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"jinja2 (>=3.1.5,<4.0.0)",
"pytest>=7.0.0",
"flask (>=3.1.0,<4.0.0)",
"pytest>=7.0.0",
"flask (>=3.1.0,<4.0.0)",
"tinydb (>=3.1.0,<4.0.0)",
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

6
senju/haiku.py Normal file
View file

@ -0,0 +1,6 @@
from dataclasses import dataclass
@dataclass
class Haiku:
lines: list[str]

View file

@ -1,11 +1,39 @@
from flask import Flask, render_template
from pathlib import Path
from flask import Flask, redirect, render_template, url_for
from senju.haiku import Haiku
from senju.store_manager import StoreManager
app = Flask(__name__)
store = StoreManager(Path("/tmp/store.db"))
@app.route("/")
def index():
context = {
"number": 1337
def index_view():
return render_template("index.html", title="Senju")
@app.route("/haiku/")
def haiku_index_view():
haiku_id: int | None = store.get_id_of_latest_haiku()
if haiku_id is None:
# TODO: add "empty haiku list" error page
raise KeyError("no haiku exist yet")
return redirect(url_for("haiku_view", haiku_id=haiku_id))
@app.route("/haiku/<int:haiku_id>")
def haiku_view(haiku_id):
haiku: Haiku | None = store.load_haiku(haiku_id)
if haiku is None:
# TODO: add "haiku not found" page
raise KeyError("haiku not found")
context: dict = {
"haiku": haiku
}
return render_template("index.jinja", context=context, title="Senju")
return render_template(
"haiku.html",
context=context,
title="Haiku of the Day")

51
senju/store_manager.py Normal file
View file

@ -0,0 +1,51 @@
from typing import Optional
from tinydb import TinyDB
from pathlib import Path
from logging import Logger
from tinydb.queries import QueryImpl
from senju.haiku import Haiku
DEFAULT_DB_PATH: Path = Path("/var/lib/senju.json")
class StoreManager:
__slots__ = "_db", "logger"
_db: TinyDB
logger: Logger
def __init__(self, path_to_db: Path = DEFAULT_DB_PATH) -> None:
self._db = TinyDB(path_to_db)
self.logger = Logger(__name__)
def _query(self, query: QueryImpl) -> list[dict]:
return self._db.search(query)
def _load(self, id: int) -> Optional[dict]:
try:
return self._db.get(doc_id=id)
except IndexError as e:
self.logger.warning(f"could not get item with id {id}: {e}")
return None
def _save(self, data: dict) -> int:
return self._db.insert(data)
def load_haiku(self, key: int) -> Optional[Haiku]:
raw_haiku: dict | None = self._load(key)
if raw_haiku is None:
return None
h = Haiku(**raw_haiku)
return h
def save_haiku(self, data: Haiku) -> int:
return self._save(data.__dict__)
def get_id_of_latest_haiku(self) -> Optional[int]:
try:
id = self._db.all()[-1].doc_id
return id
except IndexError as e:
self.logger.error(f"The database seems to be empty: {e}")
return None

View file

@ -25,12 +25,22 @@
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="#" class="rounded-md bg-gray-900 px-3 py-2 text-sm font-medium text-white"
aria-current="page">Start</a>
<a href="{{ url_for('index_view') }}"
class="rounded-md px-3 py-2 text-sm font-medium
{% if request.endpoint == 'index_view' %} bg-gray-900 text-white {% else %} text-gray-300 hover:bg-gray-700 hover:text-white {% endif %}">
Start
</a>
<a href="{{ url_for('haiku_index_view') }}"
class="rounded-md px-3 py-2 text-sm font-mediRefs: OPS-11um
{% if request.endpoint == 'haiku_view' %} bg-gray-900 text-white {% else %} text-gray-300 hover:bg-gray-700 hover:text-white {% endif %}">
Haiku
</a>
<a href="#"
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Haiku</a>
<a href="#"
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Information</a>
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">
Information
</a>
</div>
</div>
</div>
@ -114,7 +124,7 @@
</div>
</nav>
<header class="bg-violet-500 shadow-sm flex flex-row items-stretch gap-6
<header class="bg-violet-500 shadow-sm flex flex-row items-stretch gap-6
px-6 py-3">
<div class="flex-1 place-self-center">
<h1 class="text-3xl font-bold tracking-tight text-gray-900">{{ title }}</h1>

View file

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block content %}
<div class="bg-violet-900 min-h-screen flex items-center justify-center text-white">
<div class="text-center">
<div class="bg-white text-gray-900 p-10 rounded-lg shadow-lg max-w-2xl mx-auto transform -translate-y-10">
<h1 class="text-4xl font-bold text-violet-700 mb-6">{{ title }}</h1>
<p class="text-2xl italic leading-relaxed text-left">
{% for line in context.haiku.lines %}
{{ line }}<br>
{% endfor %}
</p>
</div>
<a href="{{ url_for('index_view') }}"
class=" inline-block bg-violet-600 hover:bg-violet-700 text-white font-bold py-2 px-4 rounded-lg">
Back to Home
</a>
</div>
</div>
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "base.jinja" %}
{% extends "base.html" %}
{% block content %}
<h1 class="text-center">yo mama</h1>
{% endblock %}

View file

@ -3,8 +3,15 @@ from pathlib import Path
import pytest
from senju.store_manager import StoreManager
@pytest.fixture(scope="session")
def temp_data_dir():
"""Create a temporary directory for test data"""
return Path(tempfile.mkdtemp())
@pytest.fixture(scope="session")
def store_manager(temp_data_dir):
return StoreManager(temp_data_dir / "store.json")

42
tests/test_store.py Normal file
View file

@ -0,0 +1,42 @@
# do not remove this import. This is needed for
# pytest fixtures to work
import pytest # noqa: F401
from senju.haiku import Haiku
from senju.store_manager import StoreManager # noqa: F401
def test_save_and_load_any(store_manager: StoreManager):
thing = {
"color": "blue",
"number": 19,
"inner": {
"no": "yes"
}
}
thing_id = store_manager._save(thing)
thing_loaded = store_manager._load(thing_id)
if thing_loaded is None:
assert False, "store manager load did not return anything but \
should have"
for key in thing.keys():
assert thing[key] == thing_loaded[key]
def test_save_and_load_haiku(store_manager: StoreManager):
h = Haiku(lines=["foobar", "qux"])
hid = store_manager.save_haiku(h)
h_loaded = store_manager.load_haiku(hid)
if h_loaded is None:
assert False, "store manager load_haiku did not return anything \
but should have"
assert h == h_loaded
def test_load_latest_with_empty_store(temp_data_dir):
store = StoreManager(temp_data_dir / "empty_store.json")
h = store.get_id_of_latest_haiku()
assert h is None