refactor: modernize typing annotations and docstrings

This commit is contained in:
2025-12-23 05:57:08 +01:00
parent 0fc099c4e4
commit 1098710d06
6 changed files with 45 additions and 49 deletions

View File

@@ -2,7 +2,6 @@
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from typing import Optional
from dotenv import load_dotenv from dotenv import load_dotenv
from flask import Flask from flask import Flask
@@ -26,12 +25,12 @@ def setup_logging(app: Flask) -> None:
app.logger.setLevel(logging.DEBUG) app.logger.setLevel(logging.DEBUG)
def create_app(config_name: Optional[str] = None) -> Flask: def create_app(config_name: str | None = None) -> Flask:
""" """
Application factory pattern for Flask. Application factory pattern for Flask.
Args: Args:
config_name: Optional configuration name (development, production, etc.) config_name: str | None configuration name (development, production, etc.)
Returns: Returns:
Flask application instance Flask application instance

View File

@@ -1,7 +1,7 @@
"""Blueprint for task-related routes.""" """Blueprint for task-related routes."""
import logging import logging
from datetime import datetime, date, timezone from datetime import datetime, date, timezone
from typing import Dict, Any, Tuple from typing import Any
from flask import Blueprint, render_template, request, jsonify, Response from flask import Blueprint, render_template, request, jsonify, Response
from sqlalchemy import text from sqlalchemy import text
@@ -93,7 +93,7 @@ def index() -> str:
@tasks_blueprint.route('/api/tasks', methods=['POST']) @tasks_blueprint.route('/api/tasks', methods=['POST'])
def api_create_task() -> Tuple[Response, int]: def api_create_task() -> tuple[Response, int]:
"""API endpoint to create a new task.""" """API endpoint to create a new task."""
try: try:
data = request.get_json() data = request.get_json()
@@ -142,7 +142,7 @@ def api_create_task() -> Tuple[Response, int]:
@tasks_blueprint.route('/api/tasks/<int:task_id>', methods=['GET']) @tasks_blueprint.route('/api/tasks/<int:task_id>', methods=['GET'])
def api_get_task(task_id: int) -> Tuple[Response, int]: def api_get_task(task_id: int) -> tuple[Response, int]:
"""API endpoint to get a single task.""" """API endpoint to get a single task."""
try: try:
task = Task.query.get_or_404(task_id) task = Task.query.get_or_404(task_id)
@@ -153,14 +153,14 @@ def api_get_task(task_id: int) -> Tuple[Response, int]:
@tasks_blueprint.route('/api/tasks/<int:task_id>', methods=['PUT', 'PATCH']) @tasks_blueprint.route('/api/tasks/<int:task_id>', methods=['PUT', 'PATCH'])
def api_update_task(task_id: int) -> Tuple[Response, int]: def api_update_task(task_id: int) -> tuple[Response, int]:
"""API endpoint to update a task.""" """API endpoint to update a task."""
try: try:
data = request.get_json() data = request.get_json()
if not isinstance(data, dict): if not isinstance(data, dict):
return jsonify({'error': 'Invalid JSON payload'}), 400 return jsonify({'error': 'Invalid JSON payload'}), 400
update_data: Dict[str, Any] = {} update_data: dict[str, Any] = {}
# Validate and update title # Validate and update title
if 'title' in data: if 'title' in data:
@@ -217,7 +217,7 @@ def api_update_task(task_id: int) -> Tuple[Response, int]:
@tasks_blueprint.route('/api/tasks/<int:task_id>', methods=['DELETE']) @tasks_blueprint.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def api_delete_task(task_id: int) -> Tuple[Response, int]: def api_delete_task(task_id: int) -> tuple[Response, int]:
"""API endpoint to delete a task.""" """API endpoint to delete a task."""
try: try:
delete_task(task_id) delete_task(task_id)
@@ -229,7 +229,7 @@ def api_delete_task(task_id: int) -> Tuple[Response, int]:
@tasks_blueprint.route('/api/tasks/reorder', methods=['POST']) @tasks_blueprint.route('/api/tasks/reorder', methods=['POST'])
def api_reorder_tasks() -> Tuple[Response, int]: def api_reorder_tasks() -> tuple[Response, int]:
"""API endpoint to reorder tasks.""" """API endpoint to reorder tasks."""
try: try:
data = request.get_json() data = request.get_json()

View File

@@ -1,5 +1,4 @@
"""Error handlers for Flask application.""" """Error handlers for Flask application."""
from typing import Tuple
from flask import Flask, jsonify, Response from flask import Flask, jsonify, Response
from flask_wtf.csrf import CSRFError from flask_wtf.csrf import CSRFError
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@@ -9,23 +8,23 @@ def register_error_handlers(app: Flask) -> None:
"""Register all error handlers with the Flask app.""" """Register all error handlers with the Flask app."""
@app.errorhandler(400) @app.errorhandler(400)
def bad_request(error: HTTPException) -> Tuple[Response, int]: def bad_request(error: HTTPException) -> tuple[Response, int]:
"""Handle 400 errors with JSON response.""" """Handle 400 errors with JSON response."""
return jsonify({'error': 'Bad request', 'message': str(error)}), 400 return jsonify({'error': 'Bad request', 'message': str(error)}), 400
@app.errorhandler(404) @app.errorhandler(404)
def not_found(error: HTTPException) -> Tuple[Response, int]: def not_found(error: HTTPException) -> tuple[Response, int]:
"""Handle 404 errors with JSON response.""" """Handle 404 errors with JSON response."""
return jsonify({'error': 'Not found', 'message': str(error)}), 404 return jsonify({'error': 'Not found', 'message': str(error)}), 404
@app.errorhandler(CSRFError) @app.errorhandler(CSRFError)
def handle_csrf_error(e: CSRFError) -> Tuple[Response, int]: def handle_csrf_error(e: CSRFError) -> tuple[Response, int]:
"""Handle CSRF errors with JSON response.""" """Handle CSRF errors with JSON response."""
app.logger.warning(f'CSRF error: {e}') app.logger.warning(f'CSRF error: {e}')
return jsonify({'error': 'CSRF token missing or invalid'}), 400 return jsonify({'error': 'CSRF token missing or invalid'}), 400
@app.errorhandler(500) @app.errorhandler(500)
def internal_error(error: Exception) -> Tuple[Response, int]: def internal_error(error: Exception) -> tuple[Response, int]:
"""Handle 500 errors with JSON response.""" """Handle 500 errors with JSON response."""
app.logger.error(f'Server error: {error}') app.logger.error(f'Server error: {error}')
return jsonify({'error': 'Internal server error'}), 500 return jsonify({'error': 'Internal server error'}), 500

View File

@@ -1,6 +1,6 @@
"""SQLAlchemy models for Flado.""" """SQLAlchemy models for Flado."""
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, Any from typing import Any
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
@@ -29,7 +29,7 @@ class Task(db.Model):
tags = db.relationship('Tag', secondary='task_tags', tags = db.relationship('Tag', secondary='task_tags',
back_populates='tasks', lazy='select') back_populates='tasks', lazy='select')
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> dict[str, Any]:
"""Convert task to dictionary for JSON serialization.""" """Convert task to dictionary for JSON serialization."""
return { return {
'id': self.id, 'id': self.id,

View File

@@ -1,7 +1,6 @@
"""Business logic helpers for Flado.""" """Business logic helpers for Flado."""
import logging import logging
from datetime import datetime, date, timezone from datetime import datetime, date, timezone
from typing import Optional, List
from sqlalchemy import or_, func from sqlalchemy import or_, func
from sqlalchemy.orm import Query from sqlalchemy.orm import Query
@@ -11,13 +10,13 @@ from .models import db, Task, Tag
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_tasks(filter_type: str = 'all', search_query: Optional[str] = None) -> Query: def get_tasks(filter_type: str = 'all', search_query: str | None = None) -> Query:
""" """
Retrieve tasks based on filter type. Retrieve tasks based on filter type.
Args: Args:
filter_type: 'all', 'today', 'upcoming', 'completed', 'active' filter_type: 'all', 'today', 'upcoming', 'completed', 'active'
search_query: Optional search string to filter by title/description search_query: str | None search string to filter by title/description
Returns: Returns:
Query object of tasks Query object of tasks
@@ -49,18 +48,18 @@ def get_tasks(filter_type: str = 'all', search_query: Optional[str] = None) -> Q
def create_task( def create_task(
title: str, title: str,
description: Optional[str] = None, description: str | None = None,
due_date: Optional[date] = None, due_date: date | None = None,
tag_names: Optional[List[str]] = None tag_names: list[str] | None = None
) -> Task: ) -> Task:
""" """
Create a new task. Create a new task.
Args: Args:
title: Task title (required, already validated) title: Task title (required, already validated)
description: Optional task description (already validated) description: str | None task description (already validated)
due_date: Optional due date (date object, already validated) due_date: date | None due date (date object, already validated)
tag_names: Optional list of tag names to associate (already validated) tag_names: list[str] | None list of tag names to associate (already validated)
Returns: Returns:
Created Task object Created Task object
@@ -162,12 +161,12 @@ def delete_task(task_id: int) -> bool:
raise raise
def reorder_tasks(task_ids: List[int]) -> bool: def reorder_tasks(task_ids: list[int]) -> bool:
""" """
Reorder tasks by updating their positions. Reorder tasks by updating their positions.
Args: Args:
task_ids: List of task IDs in desired order (already validated) task_ids: list[int] of task IDs in desired order (already validated)
Returns: Returns:
True if reordered successfully True if reordered successfully
@@ -209,15 +208,15 @@ def reorder_tasks(task_ids: List[int]) -> bool:
raise raise
def get_or_create_tags(tag_names: List[str]) -> List[Tag]: def get_or_create_tags(tag_names: list[str]) -> list[Tag]:
""" """
Get existing tags or create new ones. Get existing tags or create new ones.
Args: Args:
tag_names: List of tag names (already validated) tag_names: list[str] of tag names (already validated)
Returns: Returns:
List of Tag objects list[Tag] of tag objects
Note: Note:
Does not commit the session. Caller is responsible for committing. Does not commit the session. Caller is responsible for committing.
@@ -235,6 +234,6 @@ def get_or_create_tags(tag_names: List[str]) -> List[Tag]:
return tags return tags
def get_all_tags() -> List[Tag]: def get_all_tags() -> list[Tag]:
"""Get all tags.""" """Get all tags."""
return Tag.query.order_by(Tag.name.asc()).all() return Tag.query.order_by(Tag.name.asc()).all()

View File

@@ -1,9 +1,8 @@
"""Input validation utilities for Flado.""" """Input validation utilities for Flado."""
import re import re
from typing import Optional, List, Tuple
def validate_title(title: Optional[str]) -> Tuple[bool, Optional[str]]: def validate_title(title: str | None) -> tuple[bool, str | None]:
""" """
Validate task title. Validate task title.
@@ -11,7 +10,7 @@ def validate_title(title: Optional[str]) -> Tuple[bool, Optional[str]]:
title: Title to validate title: Title to validate
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if not title: if not title:
return False, "Title is required" return False, "Title is required"
@@ -26,7 +25,7 @@ def validate_title(title: Optional[str]) -> Tuple[bool, Optional[str]]:
return True, None return True, None
def validate_description(description: Optional[str]) -> Tuple[bool, Optional[str]]: def validate_description(description: str | None) -> tuple[bool, str | None]:
""" """
Validate task description. Validate task description.
@@ -34,7 +33,7 @@ def validate_description(description: Optional[str]) -> Tuple[bool, Optional[str
description: Description to validate description: Description to validate
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if description is None: if description is None:
return True, None return True, None
@@ -46,7 +45,7 @@ def validate_description(description: Optional[str]) -> Tuple[bool, Optional[str
return True, None return True, None
def validate_date_format(date_str: Optional[str]) -> Tuple[bool, Optional[str]]: def validate_date_format(date_str: str | None) -> tuple[bool, str | None]:
""" """
Validate date string format (YYYY-MM-DD). Validate date string format (YYYY-MM-DD).
@@ -54,7 +53,7 @@ def validate_date_format(date_str: Optional[str]) -> Tuple[bool, Optional[str]]:
date_str: Date string to validate date_str: Date string to validate
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if not date_str: if not date_str:
return True, None return True, None
@@ -70,7 +69,7 @@ def validate_date_format(date_str: Optional[str]) -> Tuple[bool, Optional[str]]:
return True, None return True, None
def validate_tag_name(tag_name: Optional[str]) -> Tuple[bool, Optional[str]]: def validate_tag_name(tag_name: str | None) -> tuple[bool, str | None]:
""" """
Validate tag name. Validate tag name.
@@ -78,7 +77,7 @@ def validate_tag_name(tag_name: Optional[str]) -> Tuple[bool, Optional[str]]:
tag_name: Tag name to validate tag_name: Tag name to validate
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if not tag_name: if not tag_name:
return False, "Tag name is required" return False, "Tag name is required"
@@ -97,15 +96,15 @@ def validate_tag_name(tag_name: Optional[str]) -> Tuple[bool, Optional[str]]:
return True, None return True, None
def validate_tag_names(tag_names: Optional[List[str]]) -> Tuple[bool, Optional[str]]: def validate_tag_names(tag_names: list[str] | None) -> tuple[bool, str | None]:
""" """
Validate list of tag names. Validate list of tag names.
Args: Args:
tag_names: List of tag names to validate tag_names: list[str] of tag names to validate
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if tag_names is None: if tag_names is None:
return True, None return True, None
@@ -121,7 +120,7 @@ def validate_tag_names(tag_names: Optional[List[str]]) -> Tuple[bool, Optional[s
return True, None return True, None
def validate_hex_color(color: Optional[str]) -> Tuple[bool, Optional[str]]: def validate_hex_color(color: str | None) -> tuple[bool, str | None]:
""" """
Validate hex color format. Validate hex color format.
@@ -129,7 +128,7 @@ def validate_hex_color(color: Optional[str]) -> Tuple[bool, Optional[str]]:
color: Hex color string (e.g., #RRGGBB) color: Hex color string (e.g., #RRGGBB)
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if not color: if not color:
return True, None return True, None
@@ -144,15 +143,15 @@ def validate_hex_color(color: Optional[str]) -> Tuple[bool, Optional[str]]:
return True, None return True, None
def validate_task_ids(task_ids: Optional[List[int]]) -> Tuple[bool, Optional[str]]: def validate_task_ids(task_ids: list[int] | None) -> tuple[bool, str | None]:
""" """
Validate list of task IDs. Validate list of task IDs.
Args: Args:
task_ids: List of task IDs to validate task_ids: list[int] of task IDs to validate
Returns: Returns:
Tuple of (is_valid, error_message) tuple[bool, str | None]: (is_valid, error_message)
""" """
if not task_ids: if not task_ids:
return False, "task_ids is required" return False, "task_ids is required"