mirror of
https://github.com/senju1337/senju.git
synced 2025-12-24 07:39:29 +00:00
Merge branch 'devel' into feat/container
This commit is contained in:
commit
328d76e7da
10 changed files with 193 additions and 19 deletions
14
poetry.lock
generated
14
poetry.lock
generated
|
|
@ -243,6 +243,18 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
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]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
|
|
@ -307,4 +319,4 @@ watchdog = ["watchdog (>=2.3)"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
content-hash = "45718b68919d6fafaaed35cd58dbbb244f8ebb0f0c73de8050cd04b8cae82738"
|
content-hash = "29ea72f4fd9859486c77dc4fd0668e0c55b618c56c01c23a0e2ce0a2cf75d026"
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,17 @@
|
||||||
name = "senju"
|
name = "senju"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "API / Webservice for Phrases/Words/Kanji/Haiku"
|
description = "API / Webservice for Phrases/Words/Kanji/Haiku"
|
||||||
authors = [
|
authors = [{ name = "Christoph J. Scherr", email = "software@cscherr.de" }]
|
||||||
{name = "PlexSheep",email = "software@cscherr.de"}
|
|
||||||
]
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jinja2 (>=3.1.5,<4.0.0)",
|
"jinja2 (>=3.1.5,<4.0.0)",
|
||||||
"pytest>=7.0.0",
|
"pytest>=7.0.0",
|
||||||
"flask (>=3.1.0,<4.0.0)",
|
"flask (>=3.1.0,<4.0.0)",
|
||||||
|
"tinydb (>=3.1.0,<4.0.0)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
|
||||||
6
senju/haiku.py
Normal file
6
senju/haiku.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Haiku:
|
||||||
|
lines: list[str]
|
||||||
|
|
@ -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__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
store = StoreManager(Path("/tmp/store.db"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index_view():
|
||||||
context = {
|
return render_template("index.html", title="Senju")
|
||||||
"number": 1337
|
|
||||||
|
|
||||||
|
@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
51
senju/store_manager.py
Normal 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
|
||||||
|
|
@ -25,12 +25,22 @@
|
||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
<div class="ml-10 flex items-baseline space-x-4">
|
<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" -->
|
<!-- 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"
|
<a href="{{ url_for('index_view') }}"
|
||||||
aria-current="page">Start</a>
|
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="#"
|
<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>
|
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">
|
||||||
<a href="#"
|
Information
|
||||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Information</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
20
senju/templates/haiku.html
Normal file
20
senju/templates/haiku.html
Normal 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 %}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.jinja" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="text-center">yo mama</h1>
|
<h1 class="text-center">yo mama</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -3,8 +3,15 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from senju.store_manager import StoreManager
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def temp_data_dir():
|
def temp_data_dir():
|
||||||
"""Create a temporary directory for test data"""
|
"""Create a temporary directory for test data"""
|
||||||
return Path(tempfile.mkdtemp())
|
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
42
tests/test_store.py
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue