diff --git a/poetry.lock b/poetry.lock index 8234754..72ccf61 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml index daff9bb..686def7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/senju/haiku.py b/senju/haiku.py new file mode 100644 index 0000000..15d2a28 --- /dev/null +++ b/senju/haiku.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + + +@dataclass +class Haiku: + lines: list[str] diff --git a/senju/main.py b/senju/main.py index 034f4b7..c6d9bd0 100644 --- a/senju/main.py +++ b/senju/main.py @@ -1,16 +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_view(): - return render_template("index.jinja", title="Senju") + return render_template("index.html", title="Senju") -@app.route("/haiku") -def haiku_view(): +@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/") +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( - "haiku.jinja", + "haiku.html", + context=context, title="Haiku of the Day") diff --git a/senju/store_manager.py b/senju/store_manager.py new file mode 100644 index 0000000..4886559 --- /dev/null +++ b/senju/store_manager.py @@ -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 diff --git a/senju/templates/base.jinja b/senju/templates/base.html similarity index 92% rename from senju/templates/base.jinja rename to senju/templates/base.html index d567c40..c48a7fe 100644 --- a/senju/templates/base.jinja +++ b/senju/templates/base.html @@ -25,24 +25,23 @@ -

{{ title }}

diff --git a/senju/templates/haiku.html b/senju/templates/haiku.html new file mode 100644 index 0000000..844dfff --- /dev/null +++ b/senju/templates/haiku.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

{{ title }}

+

+ {% for line in context.haiku.lines %} + {{ line }}
+ {% endfor %} +

+
+ + Back to Home + +
+
+{% endblock %} diff --git a/senju/templates/haiku.jinja b/senju/templates/haiku.jinja deleted file mode 100644 index fa1db07..0000000 --- a/senju/templates/haiku.jinja +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.jinja" %} - -{% block content %} -
-
-
-

Haiku of the Day

-

- An old silent pond
- A frog jumps into the pond—
- Splash! Silence again. -

-
- - Back to Home - -
-
-{% endblock %} - diff --git a/senju/templates/index.jinja b/senju/templates/index.html similarity index 72% rename from senju/templates/index.jinja rename to senju/templates/index.html index 4f8e829..3e1bbbf 100644 --- a/senju/templates/index.jinja +++ b/senju/templates/index.html @@ -1,4 +1,4 @@ -{% extends "base.jinja" %} +{% extends "base.html" %} {% block content %}

yo mama

{% endblock %} diff --git a/tests/conftest.py b/tests/conftest.py index efe5392..0967994 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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") diff --git a/tests/test_store.py b/tests/test_store.py new file mode 100644 index 0000000..f49b754 --- /dev/null +++ b/tests/test_store.py @@ -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