From 8a3c997cf7914944d0f241c456402765ba6382b3 Mon Sep 17 00:00:00 2001 From: Alivecow Date: Sun, 23 Mar 2025 17:06:54 +0100 Subject: [PATCH] docs: Add module level dockstrings and change other Refs: OPS-68 --- senju/haiku.py | 95 +++++++++++++++++++++++-------- senju/main.py | 95 +++++++++++++++++++------------ senju/store_manager.py | 124 ++++++++++++++++++++++++++++------------- 3 files changed, 213 insertions(+), 101 deletions(-) diff --git a/senju/haiku.py b/senju/haiku.py index 151647f..d0305c6 100644 --- a/senju/haiku.py +++ b/senju/haiku.py @@ -1,3 +1,59 @@ +""" +Haiku Generation Module +======================= + +A client interface for AI-powered haiku poem generation. + +This module provides the core functionality for communicating with an Ollama-based +AI service to generate three-line haiku poems. It handles the entire generation +process, from sending properly formatted requests to processing and validating +the returned poems. + +Classes +------- +Haiku + A dataclass representation of a haiku poem, providing structure for storage, + manipulation and serialization of poem data. + + **Methods**: + + * ``to_json()``: Converts a haiku instance to JSON format for API responses + * ``generate_haiku(seed_text)``: Creates a new haiku using the AI service + +Constants +--------- +AI_SERVICE_URL + The endpoint URL for the Ollama API service. + +AI_MODEL_NAME + The specific AI model used for haiku generation. + +REQUEST_TIMEOUT + The maximum time (in seconds) to wait for AI service responses. + +Dependencies +------------ +* requests: HTTP client library for API communication +* dataclasses: Support for the Haiku data structure +* logging: Error and diagnostic information capture +* json: Processing of API responses + +Implementation Details +---------------------- +The module implements a robust communication pattern with the AI service, including: + +1. Proper request formatting with seed text integration +2. Multiple retry attempts for handling temporary service issues +3. Response validation to ensure the returned text follows haiku structure +4. Fallback mechanisms when the AI service returns unsuitable content +5. JSON serialization for consistent data exchange + +When communicating with the AI service, the module maintains appropriate +error handling and logging to help diagnose any generation issues. It aims +to provide a reliable haiku generation experience even when dealing with the +inherent unpredictability of AI-generated content. +""" + from __future__ import annotations import json @@ -19,73 +75,64 @@ def foobar(): @dataclass class Haiku: + """ + A class representing a haiku poem with three lines. + + :ivar lines: A list containing the three lines of the haiku. + :type lines: list[str] + """ lines: list[str] def get_json(self): """ Converts the haiku lines to a JSON string. - Returns: - str: A JSON string representation of the haiku lines. + :return: A JSON string representation of the haiku lines. + :rtype: str """ - return json.dumps(self.lines) @staticmethod - def request_haiku(seed: str) -> Haiku: + def request_haiku(seed: str) -> 'Haiku': """ Generates a haiku using an AI model based on the provided seed text. This function prompts the AI to generate a haiku based on the user input. - It validates that the response: - - Contains exactly 3 lines - + It validates that the response contains exactly 3 lines. The function will retry until a valid haiku is generated. - Args: - seed (str): The input text used to inspire the haiku generation. + :param seed: The input text used to inspire the haiku generation. + :type seed: str + :return: A new Haiku object containing the generated three lines. + :rtype: Haiku - Returns: - Haiku: A new Haiku object containing the generated three lines. - - Raises: - Possible JSONDecodeError which is caught and handled with retries. + :raises: Possible JSONDecodeError which is caught and handled with retries. """ - ai_gen_request = { "model": "haiku", "prompt": f"{seed}", "stream": False, "eval_count": 20 } - while True: try: r = requests.post(url=AI_BASE_URL + AI_GEN_ENDPOINT, json=ai_gen_request) ai_response = str(r.json()["response"]) - logging.warning(ai_response) - lines = ai_response.split("\n") - for _ in range(0, 2): lines.pop() - logging.warning(lines) - if len(lines) != 3: continue - haiku = Haiku( [ lines[0], lines[1], lines[2] ]) - break except json.JSONDecodeError: continue - return haiku diff --git a/senju/main.py b/senju/main.py index a457e58..99d90a9 100644 --- a/senju/main.py +++ b/senju/main.py @@ -1,3 +1,42 @@ +""" +Senju Haiku Web Application +=========================== + +A Flask-based web interface for generating, viewing, and managing haiku poetry. + +This application provides a comprehensive interface between users and an AI-powered +haiku generation service, with persistent storage capabilities. Users can interact +with the system through both a web interface and a RESTful API. + +Features +-------- +* **Landing page**: Welcome interface introducing users to the Senju service +* **Browsing interface**: Gallery-style viewing of previously generated haikus +* **Prompt interface**: Text input system for generating haikus from seed text +* **Image scanning**: Experimental interface for creating haikus from visual inputs +* **RESTful API**: Programmatic access for integration with other services + +Architecture +------------ +The application implements a RESTful architecture using Flask's routing system +and template rendering. All user interactions are handled through clearly defined +routes, with appropriate error handling for exceptional cases. + +Dependencies +------------ +* future.annotations: Enhanced type hint support +* os, Path: Filesystem operations for storage management +* Flask: Core web application framework +* Haiku: Custom class for poem representation and generation +* StoreManager: Database abstraction for persistence operations + +Implementation +-------------- +The module initializes both a Flask application instance and a StoreManager +with a configured storage location. All routes and view functions required +for the complete web interface are defined within this module. +""" + from __future__ import annotations import os @@ -26,32 +65,26 @@ def index_view(): """ Render the main index page of the application. - Returns: - Text: The index.html template with title "Senju". + :return: The index.html template with title "Senju". + :rtype: flask.Response """ - return render_template("index.html", title="Senju") - @app.route("/haiku/") def haiku_index_view(): """ Redirect to the most recently created haiku. - Returns: - Response: Redirects to the haiku_view route with the latest haiku ID. - - Raises: - KeyError: If no haikus exist in the store yet. + :return: Redirects to the haiku_view route with the latest haiku ID. + :rtype: flask.Response + :raises KeyError: If no haikus exist in the store yet. """ - 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): """ @@ -61,16 +94,12 @@ def haiku_view(haiku_id): the haiku.html template. If no haiku is found with the provided ID, raises a KeyError. - Args: - haiku_id (int): The ID of the haiku to display. - - Returns: - Text: The haiku.html template with the haiku data in context. - - Raises: - KeyError: If no haiku exists with the given ID. + :param haiku_id: The ID of the haiku to display. + :type haiku_id: int + :return: The haiku.html template with the haiku data in context. + :rtype: flask.Response + :raises KeyError: If no haiku exists with the given ID. """ - haiku: Haiku | None = store.load_haiku(haiku_id) if haiku is None: # TODO: add "haiku not found" page @@ -78,42 +107,37 @@ def haiku_view(haiku_id): context: dict = { "haiku": haiku } - return render_template( "haiku.html", context=context, title="Haiku of the Day") - @app.route("/prompt") def prompt_view(): """ Render the haiku generation prompt page. - Returns: - Text: The prompt.html template with title "Haiku generation". + :return: The prompt.html template with title "Haiku generation". + :rtype: flask.Response """ - return render_template( "prompt.html", title="Haiku generation" ) - @app.route("/scan") def scan_view(): """ Render the image scanning page. - Returns: - Text: The scan.html template with title "Image scanning". + :return: The scan.html template with title "Image scanning". + :rtype: flask.Response """ return render_template( "scan.html", title="Image scanning" ) - @app.route("/api/v1/haiku", methods=['POST']) def generate_haiku(): """ @@ -123,11 +147,10 @@ def generate_haiku(): Generates a haiku using the prompt, saves it to the store, and returns the ID. - Returns: - str: The ID of the newly created haiku if method is POST. - tuple: Error message and status code 405 if method is not POST. + :return: The ID of the newly created haiku if method is POST. + Error message and status code 405 if method is not POST. + :rtype: Union[str, Tuple[str, int]] """ - if request.method == 'POST': json_data = request.get_json() prompt = json_data["prompt"] @@ -137,16 +160,14 @@ def generate_haiku(): else: return "Method not allowed", 405 - @app.route('/favicon.ico') def favicon(): """ Serve the favicon.ico file from the static directory. - Returns: - Response: The favicon.ico file with the appropriate MIME type. + :return: The favicon.ico file with the appropriate MIME type. + :rtype: flask.Response """ - return send_from_directory(os.path.join(app.root_path, 'static/img'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') diff --git a/senju/store_manager.py b/senju/store_manager.py index 8e418cd..883e3e9 100644 --- a/senju/store_manager.py +++ b/senju/store_manager.py @@ -1,3 +1,47 @@ +""" +Senju Database Management Module +================================ + +A database interaction layer for the Senju haiku management system. + +This module implements a lightweight document database abstraction using TinyDB +for persistent storage of haiku poems. It provides a clean interface for storing, +retrieving, updating, and managing haiku entries in the system. + +Classes +------- +StoreManager + The primary class responsible for all database operations. Handles connection + management, CRUD operations, and query capabilities for haiku data. + +Functions +--------- +utility_function + Provides simple arithmetic operations to support database functionalities. + +Constants +--------- +DEFAULT_DB_PATH + The default filesystem location for the TinyDB database file (/var/lib/senju.json). + +Dependencies +------------ +* future.annotations: Enhanced type hint support +* logging.Logger: Diagnostic and error logging capabilities +* pathlib.Path: Cross-platform filesystem path handling +* typing.Optional: Type annotations for nullable values +* tinydb.TinyDB: Lightweight document database implementation +* tinydb.QueryImpl: Query builder for database searches +* senju.haiku.Haiku: Data model for haiku representation + +Implementation Details +---------------------- +The module uses TinyDB as its storage engine, providing a JSON-based document +storage solution that balances simplicity with functionality. The StoreManager +abstracts all database operations behind a clean API, handling connection lifecycle +and providing methods for common operations on haiku data. +""" + from __future__ import annotations from logging import Logger @@ -20,6 +64,17 @@ def foobar(): class StoreManager: + """ + Manages the storage and retrieval of haiku data using TinyDB. + + This class provides an interface for saving and loading haikus from + a TinyDB database file. + + :ivar _db: Database instance for storing haiku data. + :type _db: TinyDB + :ivar logger: Logger for tracking operations and errors. + :type logger: Logger + """ __slots__ = "_db", "logger" _db: TinyDB logger: Logger @@ -28,9 +83,9 @@ class StoreManager: """ Initialize the StoreManager with a database path. - Args: - path_to_db (Path, optional): Path to the TinyDB database file. - Defaults to DEFAULT_DB_PATH. + :param path_to_db: Path to the TinyDB database file. Defaults to DEFAULT_DB_PATH. + :type path_to_db: Path, optional + :return: None """ self._db = TinyDB(path_to_db) self.logger = Logger(__name__) @@ -39,29 +94,25 @@ class StoreManager: """ Execute a query against the database. - Args: - query (QueryImpl): TinyDB query to execute. - - Returns: - list[dict]: List of documents matching the query. + :param query: TinyDB query to execute. + :type query: QueryImpl + :return: List of documents matching the query. + :rtype: list[dict] """ - return self._db.search(query) def _load(self, id: int) -> Optional[dict]: """ Load a document by its ID. - Args: - id (int): Document ID to load. + :param id: Document ID to load. + :type id: int + :return: The document if found, None otherwise. + :rtype: Optional[dict] - Returns: - Optional[dict]: The document if found, None otherwise. - - Logs: - Warning if document with specified ID is not found. + .. note:: + Logs a warning if document with specified ID is not found. """ - try: return self._db.get(doc_id=id) except IndexError as e: @@ -72,26 +123,22 @@ class StoreManager: """ Save a document to the database. - Args: - data (dict): Document data to save. - - Returns: - int: The document ID of the saved document. + :param data: Document data to save. + :type data: dict + :return: The document ID of the saved document. + :rtype: int """ - return self._db.insert(data) def load_haiku(self, key: int) -> Optional[Haiku]: """ Load a haiku by its ID. - Args: - key (int): The ID of the haiku to load. - - Returns: - Optional[Haiku]: A Haiku object if found, None otherwise. + :param key: The ID of the haiku to load. + :type key: int + :return: A Haiku object if found, None otherwise. + :rtype: Optional[Haiku] """ - raw_haiku: dict | None = self._load(key) if raw_haiku is None: return None @@ -102,26 +149,23 @@ class StoreManager: """ Save a haiku to the database. - Args: - data (Haiku): The Haiku object to save. - - Returns: - int: The document ID of the saved haiku. + :param data: The Haiku object to save. + :type data: Haiku + :return: The document ID of the saved haiku. + :rtype: int """ - return self._save(data.__dict__) def get_id_of_latest_haiku(self) -> Optional[int]: """ Get the ID of the most recently added haiku. - Returns: - Optional[int]: The ID of the latest haiku if any exists, None otherwise. + :return: The ID of the latest haiku if any exists, None otherwise. + :rtype: Optional[int] - Logs: - Error if the database is empty. + .. note:: + Logs an error if the database is empty. """ - try: id = self._db.all()[-1].doc_id return id